Анализ автомобильного рынка Казахстана¶

План работы

  • 1  Цель
  • 2  Задачи
  • 3  Описание данных
  • 4  Изучение общей информации
  • 5  Предобработка данных
    • 5.1  Удаление столбцов
    • 5.2  Переименование названий столбцов
    • 5.3  Проверка на явные дубликаты
    • 5.4  Проверка на неявные дубликаты, аномалии и пропуски
      • 5.4.1  Создаю функции для проверки столбцов.
      • 5.4.2  Столбец 'year'
      • 5.4.3  Столбец 'month'
      • 5.4.4  Столбец 'company'
      • 5.4.5  Столбец 'brand'
      • 5.4.6  Столбец 'model'
      • 5.4.7  Столбец 'year_of_release'
        • 5.4.7.1  Проверка на неявные дубликаты
        • 5.4.7.2  Обработка пропусков
      • 5.4.8  Столбец 'producing_country'
      • 5.4.9  Столбец 'type_of_fuel'
        • 5.4.9.1  Работа с аномальными значениями
        • 5.4.9.2  Работа с неявными дубликатами
        • 5.4.9.3  Заполнение пропусков
      • 5.4.10  Столбец 'engine_volume_l'
        • 5.4.10.1  Работа с аномальными значениями
        • 5.4.10.2  Заполнение пропусков
      • 5.4.11  Столбец 'transmission'
        • 5.4.11.1  Заполнение пропусков
      • 5.4.12  Столбец 'type_of_drive'
        • 5.4.12.1  Удаление аномалий
        • 5.4.12.2  Работа с явными дубликатами
      • 5.4.13  Столбцы 'price_usd' и 'sale_usd'
      • 5.4.14  Столбец 'quantity'
      • 5.4.15  Столбец 'client_type'
        • 5.4.15.1  Работа с неявными дубликатами
        • 5.4.15.2  Работа с пропусками
      • 5.4.16  Столбец 'area'
      • 5.4.17  Столбец 'segmentation'
      • 5.4.18  Столбец 'class_2013'
    • 5.5  Создание столбца год и месяц продажи
    • 5.6  Расчет доли удаленных данных и количества оставшихся пропусков
    • 5.7  Выводы
  • 6  Исследовательский анализ
    • 6.1  Распределение количества брендов на компанию
    • 6.2  Распределение количества моделей у брендов
    • 6.3  Распределение количества автомобилей по году выпуска
    • 6.4  Распределение количества проданных автомобилей по странам
    • 6.5  Распределение количества проданных машин по типу двигателя
    • 6.6  Распределение автомобилей по объему двигателя
    • 6.7  Распределение типов трансмиссии по проданным автомобилям
    • 6.8  Распределение проданных автомобилей по типу привода
    • 6.9  Распределение количества проданных автомобилей по типу клиента
    • 6.10  Распределение количества автомобилей в заказе
    • 6.11  Распределение цен
    • 6.12  Распределение продаж по классу автомобилей
    • 6.13  Категоризация данных
      • 6.13.1  Столбец 'engine_volume_l'
      • 6.13.2  Столбец 'price_usd'
    • 6.14  Корреляционный анализ
    • 6.15  Выводы
  • 7  Анализ рынка продаж автомобилей в Казахстане в 2019 году
    • 7.1  Емкость рынка
      • 7.1.1  Емкость для всего рынка
      • 7.1.2  Емкость коммерческого транспорта на рынке
      • 7.1.3  Емкость некоммерческого транспорта на рынке
      • 7.1.4  Диаграммы долей коммерческого и некоммерческого транспорта на рынке
      • 7.1.5  Выводы
    • 7.2  Определение марок - лидеров на рынке автомобилей
      • 7.2.1  Марки-лидеры по всему рынку
      • 7.2.2  Расчет марок-лодеров коммерческого и некоммерческого транспорта
    • 7.3  Динамика рынка автомобилей в Казахстане за 2019 год
      • 7.3.1  Общая динамика рынка
      • 7.3.2  Динамика объемов продаж коммерческих и некоммерческих автомобилей
      • 7.3.3  Динамика продаж автомобилей по сегментам
    • 7.4  Продажи автомобилей в разрезе бренд/сегмент
    • 7.5  Продажи по маркам в разрезе коммерческого и некоммерческого транспорта
    • 7.6  Продажи по регионам
    • 7.7  Продажи автоцентров
    • 7.8  Модели-лидеры на рынке
    • 7.9  Выводы
  • 8  Анализ положения Меркур Авто
    • 8.1  Выручка
    • 8.2  Продажи по регионам
    • 8.3  Конкурентный анализ
    • 8.4  Матрица Boston Consulting Group
    • 8.5  Выводы
  • 9  Предложения для Меркурий Авто

Цель¶

Провести очистку данных. Определить рыночные сегменты Меркур Авто и разработать маркетинговый план.

Задачи¶

  • Предобработка данных;
  • Исследовательский анализ данных;
  • Анализ рынка автомобилей Казахстана;
  • Анализ позиции Меркур Авто: внутрення и внешняя среда.

Описание данных¶

Для решения поставленной задачи мне предоставили датасет, в котором есть следующие столбцы:

  • Год – год продажи (2019)
  • Месяц – месяц продажи (январь - сентябрь)
  • Компания – название автоцентра
  • Бренд – название продаваемой марки автомобиля
  • Модель – название модели автомобиля
  • Модификация – модификация модели автомобиля (удаляем)
  • Год выпуска – год производства автомобиля
  • Страна-производитель – страна, где произведен автомобиль
  • Вид топлива – бензин, дизель, электричество, гибрид
  • Объём двиг л – объем двигателя автомобиля в литрах
  • Коробка передач – тип коробки переключения передач (оставляем два варианта: автоматическая, механическая, то есть все что не механика ставим автомат, на DSG, S-Tronic и прочее делить не надо, равно как и количество передач)
  • Тип привода – в итоге оставляем RWD – задний привод, FWD – передний привод, 4WD – полный привод, 2WD – все остальное (подключаемый полный привод и где нет четкого указания передний или задний это привод)
  • Сегмент – сегмент, к которому относится авто (удаляем)
  • Регион – регион продажи
  • Наименование дилерского центра – совпадает с компанией – можно удалить
  • Тип клиента – юридическое или физическое лицо (в рамках анализа не критично – можно удалить)
  • Форма расчета – наличный и безналичный расчет (много пропусков – можно удалять)
  • Количество – количество автомобилей в заказе
  • Цена USD – цена автомобиля
  • Продажа USD – цена заказа (цена авто умноженная на количество и за вычетом скидок если есть)
  • Область – область продажи
  • Сегментация 2013 – сегмент автомобиля актуальный
  • Класс 2013 – класс автомобиля актуальный
  • Сегментация Eng – английская сегментация (удаляем)
  • Локализация производства – удаляем (совпадает со страной производителем)

Изучение общей информации¶

In [321]:
# устанавливаю библиотеку транслитератор
!pip install transliterate
!pip install phik

# подключение необходимых юиюлиотек для исследования
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import re
import statistics as stat
import itertools
import seaborn as sns
import warnings
import phik
import plotly.graph_objects as go
import plotly.express as px
import plotly.io as pio
from plotly.subplots import make_subplots
from IPython.display import display_html
from phik.report import plot_correlation_matrix
from transliterate import translit
Requirement already satisfied: transliterate in c:\users\user\anaconda3\lib\site-packages (1.10.2)
Requirement already satisfied: six>=1.1.0 in c:\users\user\anaconda3\lib\site-packages (from transliterate) (1.16.0)
Requirement already satisfied: phik in c:\users\user\anaconda3\lib\site-packages (0.12.3)
Requirement already satisfied: scipy>=1.5.2 in c:\users\user\anaconda3\lib\site-packages (from phik) (1.9.1)
Requirement already satisfied: numpy>=1.18.0 in c:\users\user\anaconda3\lib\site-packages (from phik) (1.21.5)
Requirement already satisfied: joblib>=0.14.1 in c:\users\user\anaconda3\lib\site-packages (from phik) (1.1.0)
Requirement already satisfied: pandas>=0.25.1 in c:\users\user\anaconda3\lib\site-packages (from phik) (1.4.4)
Requirement already satisfied: matplotlib>=2.2.3 in c:\users\user\anaconda3\lib\site-packages (from phik) (3.5.2)
Requirement already satisfied: fonttools>=4.22.0 in c:\users\user\anaconda3\lib\site-packages (from matplotlib>=2.2.3->phik) (4.25.0)
Requirement already satisfied: pillow>=6.2.0 in c:\users\user\anaconda3\lib\site-packages (from matplotlib>=2.2.3->phik) (9.2.0)
Requirement already satisfied: cycler>=0.10 in c:\users\user\anaconda3\lib\site-packages (from matplotlib>=2.2.3->phik) (0.11.0)
Requirement already satisfied: packaging>=20.0 in c:\users\user\anaconda3\lib\site-packages (from matplotlib>=2.2.3->phik) (21.3)
Requirement already satisfied: pyparsing>=2.2.1 in c:\users\user\anaconda3\lib\site-packages (from matplotlib>=2.2.3->phik) (3.0.9)
Requirement already satisfied: kiwisolver>=1.0.1 in c:\users\user\anaconda3\lib\site-packages (from matplotlib>=2.2.3->phik) (1.4.2)
Requirement already satisfied: python-dateutil>=2.7 in c:\users\user\anaconda3\lib\site-packages (from matplotlib>=2.2.3->phik) (2.8.2)
Requirement already satisfied: pytz>=2020.1 in c:\users\user\anaconda3\lib\site-packages (from pandas>=0.25.1->phik) (2022.1)
Requirement already satisfied: six>=1.5 in c:\users\user\anaconda3\lib\site-packages (from python-dateutil>=2.7->matplotlib>=2.2.3->phik) (1.16.0)
In [238]:
# сохраняю датасет в переменную data
try:
    data = pd.read_csv('C:/Users/USER/Desktop/Исходники/Анализ автомобильного рынка Казахстана/auto_kz_2019_датасет.csv',\
                       sep=';')
except:
    print('FileNotFoundError')
In [239]:
# cброс ограничений на число столбцов
pd.set_option('display.max_columns', None)

# настраиваю количество знаков десятичных разрядов
pd.set_option('display.float_format', '{:.2f}'.format)

# устанавливаю стили визуализации
sns.set_style('darkgrid')
plt.style.use('ggplot')
pio.templates.default = 'seaborn' 

# отключаю предупреждения
warnings.filterwarnings("ignore")
In [240]:
# функция для изучения информации по датасету
def dataset(set):
    print('10 первых строк датасета')
    display(set.head(10))
    print('_____________________________________________________________')

    print('Общая информация по датасету')
    display(set.info())
    print('_____________________________________________________________')

    print('Числовое описание данных')
    display(set.describe())
    print('_____________________________________________________________')

    print('Количество пропусков')
    display(set.isna().sum())
    print('_____________________________________________________________')

    print('Kоличество пропусков для каждого столбца в процентах')
    display(pd.DataFrame(round(set.isna().mean()*100, 2)).style.background_gradient('coolwarm'))
    print('_____________________________________________________________')

    print('Число явных дубликатов:')
    display(set.duplicated().sum())
    print('_____________________________________________________________')
    
    print('Количество строк и столбцов в датасете')
    display(set.shape)
    print('_____________________________________________________________')
    
    all_lines = len(set) # расчет количества строк для дальнейших расчетов
In [241]:
dataset(data)
10 первых строк датасета
Год Месяц Компания Бренд Модель Модификация Год выпуска Страна-производитель Вид топлива Объём двиг, л, Коробка передач Тип привода Сегмент Регион Наименование дилерского центра Тип клиента Форма расчета Количество Цена, USD Продажа, USD Область Сегментация 2013 Класс 2013 Сегментация Eng Локализация производства
0 2019 Май Mercur Auto Audi A3 TFSI 2018 Германия Бензин 1,4 S-tronic передний Легковые автомобили Premium Алматы Mercur Auto Алматы Физ. Лицо безналичный 1.00 28115 28115 г.Алматы Легковые автомобили C класс C Импорт
1 2019 Август Mercur Auto Audi A3 TFSI 2018 Германия Бензин 1,4 S-tronic передний Легковые автомобили Premium Алматы Mercur Auto Алматы Юр. Лицо наличный 1.00 32246,99 32246,99 г.Алматы Легковые автомобили C класс C Импорт
2 2019 Апрель Mercur Auto Audi A4 TFSI 2018 Германия Бензин 1,4 S-Tronic FWD Легковые автомобили Premium Алматы Mercur Auto Алматы Физ. Лицо безналичный 1.00 32000 32000 г.Алматы Легковые автомобили D класс D Импорт
3 2019 Июль Mercur Auto Audi A4 TFSI 2018 Германия Бензин 1,4 S-tronic передний Легковые автомобили Premium Алматы Mercur Auto Алматы Юр. Лицо безналичный 1.00 31929 31929 г.Алматы Легковые автомобили D класс D Импорт
4 2019 Июль Mercur Auto Audi A4 TFSI 2018 Германия Бензин 1,4 S-tronic передний Легковые автомобили Premium Алматы Mercur Auto Алматы Физ. Лицо наличный 1.00 31929 31929 г.Алматы Легковые автомобили D класс D Импорт
5 2019 Февраль Mercur Auto Audi A6 TFSI 2017 Германия Бензин 2 S-Tronic quattro Легковые автомобили Premium Нур-Султан Mercur Auto Астана Физ. Лицо Наличная 1.00 42608,25485 42608,25485 г.Нур-Султан Легковые автомобили E класс E Импорт
6 2019 Май Mercur Auto Audi A6 TFSI 2019 Германия Бензин 3 S-tronic quattro Легковые автомобили Premium Алматы Mercur Auto Алматы Физ. Лицо наличный 1.00 82344 82344 г.Алматы Легковые автомобили E класс E Импорт
7 2019 Июнь Mercur Auto Audi A6 TFSI 2019 Германия Бензин 3 S-tronic quattro Легковые автомобили Premium Алматы Mercur Auto Алматы Физ. Лицо наличный 1.00 78940 78940 г.Алматы Легковые автомобили E класс E Импорт
8 2019 Июнь Mercur Auto Audi A6 TFSI 2019 Германия Бензин 3 S-tronic quattro Легковые автомобили Premium Алматы Mercur Auto Алматы Физ. Лицо наличный 1.00 78940 78940 г.Алматы Легковые автомобили E класс E Импорт
9 2019 Июнь Mercur Auto Audi A6 TFSI Allroad 2017 Германия Бензин 3 S-tronic quattro Легковые автомобили Premium Алматы Mercur Auto Алматы Юр. Лицо безналичный 1.00 64474 64474 г.Алматы Легковые автомобили E класс E Импорт
_____________________________________________________________
Общая информация по датасету
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 39966 entries, 0 to 39965
Data columns (total 25 columns):
 #   Column                          Non-Null Count  Dtype  
---  ------                          --------------  -----  
 0   Год                             39966 non-null  int64  
 1   Месяц                           39966 non-null  object 
 2   Компания                        39966 non-null  object 
 3   Бренд                           39966 non-null  object 
 4   Модель                          39966 non-null  object 
 5   Модификация                     36375 non-null  object 
 6   Год выпуска                     39465 non-null  object 
 7   Страна-производитель            39966 non-null  object 
 8   Вид топлива                     36826 non-null  object 
 9   Объём двиг, л,                  35708 non-null  object 
 10  Коробка передач                 36711 non-null  object 
 11  Тип привода                     35677 non-null  object 
 12  Сегмент                         33205 non-null  object 
 13  Регион                          39966 non-null  object 
 14  Наименование дилерского центра  39966 non-null  object 
 15  Тип клиента                     32919 non-null  object 
 16  Форма расчета                   14038 non-null  object 
 17  Количество                      39960 non-null  float64
 18  Цена, USD                       39966 non-null  object 
 19  Продажа, USD                    39966 non-null  object 
 20  Область                         39966 non-null  object 
 21  Сегментация 2013                39966 non-null  object 
 22  Класс 2013                      39966 non-null  object 
 23  Сегментация Eng                 39966 non-null  object 
 24  Локализация производства        39966 non-null  object 
dtypes: float64(1), int64(1), object(23)
memory usage: 7.6+ MB
None
_____________________________________________________________
Числовое описание данных
Год Количество
count 39966.00 39960.00
mean 2019.00 1.35
std 0.00 2.37
min 2019.00 -1.00
25% 2019.00 1.00
50% 2019.00 1.00
75% 2019.00 1.00
max 2019.00 115.00
_____________________________________________________________
Количество пропусков
Год                                   0
Месяц                                 0
Компания                              0
Бренд                                 0
Модель                                0
Модификация                        3591
Год выпуска                         501
Страна-производитель                  0
Вид топлива                        3140
Объём двиг, л,                     4258
Коробка передач                    3255
Тип привода                        4289
Сегмент                            6761
Регион                                0
Наименование дилерского центра        0
Тип клиента                        7047
Форма расчета                     25928
Количество                            6
Цена, USD                             0
Продажа, USD                          0
Область                               0
Сегментация 2013                      0
Класс 2013                            0
Сегментация Eng                       0
Локализация производства              0
dtype: int64
_____________________________________________________________
Kоличество пропусков для каждого столбца в процентах
  0
Год 0.000000
Месяц 0.000000
Компания 0.000000
Бренд 0.000000
Модель 0.000000
Модификация 8.990000
Год выпуска 1.250000
Страна-производитель 0.000000
Вид топлива 7.860000
Объём двиг, л, 10.650000
Коробка передач 8.140000
Тип привода 10.730000
Сегмент 16.920000
Регион 0.000000
Наименование дилерского центра 0.000000
Тип клиента 17.630000
Форма расчета 64.880000
Количество 0.020000
Цена, USD 0.000000
Продажа, USD 0.000000
Область 0.000000
Сегментация 2013 0.000000
Класс 2013 0.000000
Сегментация Eng 0.000000
Локализация производства 0.000000
_____________________________________________________________
Число явных дубликатов:
18698
_____________________________________________________________
Количество строк и столбцов в датасете
(39966, 25)
_____________________________________________________________

Выводы:

  1. В датасете 25 столбцов и 39966 строк.
  2. Переименую столбцы с русского на английский с использованием змеиного регистра.
  3. Надо изменить типы данных в столбцах: "Модификация", "Год выпуска", "Вид топлива", "Вид топлива", "Коробка передач", "Тип привода", "Сегмент", "Тип клиента", "Форма расчета", "Количество".
  4. Обработать пропуски в столбцах.
  5. Видно, что датасет деперсонализирован - в нем нет id покупателей.
  6. Данные имеют явные дубликаты.
  7. Проверить значения на аномалии.
  8. Надо удалить столбцы, которые не нужны для данного исследования или имеют дублирование значений из других столбцов.

Предобработка данных¶

Для дальнейших расчетов понадобится число строк датасета до предобработки, поэтому это значение сохраню в переменную all_lines.

In [242]:
all_lines = len(data)

Удаление столбцов¶

Удаляю столбцы:

  • "Сегментация Eng", так как для Казахстана актуальна такая же сегментация автомобилей, как и в России.
  • "Форма расчета", так как в нем много пропусков, которые никак не заполнить.
  • "Сегмент". В нем содержится название сегмента, к которому относится автомобиль. Но разделение на такие сегменты, которые указаны в этом столбце, не актуальны для Казахстана. Разбивка на актуальные сегменты находится в столбце "Сегментация 2013".
  • "Локализация производства". Потому что в нем данные совпадают со столбцом "Страна-производитель".
  • "Наименование дилерского центра", так как совпадает со столбцом "Компания".
  • "Модификация". Так как эта информация нужна для тех, кто работает внутри той или иной дилерской компании.
  • "Регион". В нем собраны только города, а они уже входят в состав той или иной области, где происходила продажа.
In [243]:
data = data.drop(['Сегментация Eng', 'Форма расчета', 'Сегмент', \
                   'Локализация производства', 'Наименование дилерского центра', 'Регион', 'Модификация'], axis=1)
data.head()
Out[243]:
Год Месяц Компания Бренд Модель Год выпуска Страна-производитель Вид топлива Объём двиг, л, Коробка передач Тип привода Тип клиента Количество Цена, USD Продажа, USD Область Сегментация 2013 Класс 2013
0 2019 Май Mercur Auto Audi A3 2018 Германия Бензин 1,4 S-tronic передний Физ. Лицо 1.00 28115 28115 г.Алматы Легковые автомобили C класс
1 2019 Август Mercur Auto Audi A3 2018 Германия Бензин 1,4 S-tronic передний Юр. Лицо 1.00 32246,99 32246,99 г.Алматы Легковые автомобили C класс
2 2019 Апрель Mercur Auto Audi A4 2018 Германия Бензин 1,4 S-Tronic FWD Физ. Лицо 1.00 32000 32000 г.Алматы Легковые автомобили D класс
3 2019 Июль Mercur Auto Audi A4 2018 Германия Бензин 1,4 S-tronic передний Юр. Лицо 1.00 31929 31929 г.Алматы Легковые автомобили D класс
4 2019 Июль Mercur Auto Audi A4 2018 Германия Бензин 1,4 S-tronic передний Физ. Лицо 1.00 31929 31929 г.Алматы Легковые автомобили D класс

Переименование названий столбцов¶

Переименую названия столбцов на английский язык с использованием змеиного регистра.

In [244]:
data = data.rename(columns={
    'Год': 'year',
    'Месяц': 'month',
    'Компания': 'company',
    'Бренд': 'brand',
    'Модель': 'model',
    'Год выпуска': 'year_of_release',
    'Страна-производитель': 'producing_country',
    'Вид топлива': 'type_of_fuel',
    'Объём двиг, л,': 'engine_volume_l',
    'Коробка передач': 'transmission',
    'Тип привода': 'type_of_drive',
    'Тип клиента': 'client_type',
    'Количество': 'quantity',
    'Цена, USD': 'price_usd',
    'Продажа, USD': 'sale_usd',
    'Область': 'area',
    'Сегментация 2013': 'segmentation',
    'Класс 2013': 'class_2013'
})

Проверка на явные дубликаты¶

In [245]:
data.duplicated().sum()
Out[245]:
22412

После удаления столбцов количество явных дубликатов увеличилось. Теперь их 20848. Предполагаю, что эти дубликаты возникли из-за деперсонализации, если бы были указаны id полных повторов бы не было. Потом что разные люди могут покупать одинаковые автомобили.

Проверка на неявные дубликаты, аномалии и пропуски¶

Создаю функции для проверки столбцов.¶

In [246]:
# функция для удаления лишних пробелов в строках
def space(df, column):
    df[column] = df[column].str.strip()
In [247]:
# функция для приведения значений к нижнему регистру
def low(df, column):
    df[column] = df[column].str.lower()
In [248]:
# функция для проверки на неявные дубликаты
def dubl(df, column):  # 1-ый атрибут - переменная, харанящая датасет, 2-ая - название столбца
    print("Число уникальных значений:", df[column].nunique())  # вывожу на экран количество уникальных значений
    print("Уникальные значения:\n", df[column].unique())  # вывожу на экран группировку
In [249]:
# функция от неявных дубликатов и других неподходящих значений
def implicit_dubl(df, column, dictionary):
    df[column] = df[column].replace(dictionary, regex=True)
    print("Количество уникальных значений после очистки:", df[column].nunique())
In [250]:
# функция для заполнения пропусков модой
def change_mode(df, group1, group2, change_column):
    df[change_column] = df.groupby([group1, group2])[change_column]\
    .transform(lambda x: x.fillna(x.mode()[0]) if len(x.mode()) != 0 else x)
    print('Количество незаполненных строк:', len(df[df[change_column].isna()]))
In [251]:
# функция для заполнения пропусков в столбцах по списку
def change_isna(df, cut_by_column, column, lis, replace_by):
    df.loc[data[cut_by_column].isin(lis), column] = replace_by
In [252]:
# функция для удаления всех пробелов в значении
def space_del_all(df, column):
    df[column] = df[column].replace(r' ', '', regex=True)
In [253]:
# функция для замены значений с помощью регулярных выражений
def re_sub(pat, df, column, name):
    for i in pat:
        df[column] = df[column].transform(lambda x: re.sub(i, name, str(x)))

Столбец 'year'¶

In [254]:
# вызываю функцию, чтобы выявить неявные дубликаты
dubl(data, 'year')
Число уникальных значений: 1
Уникальные значения:
 [2019]

Как и заявлено в ТЗ данные предоставлены только за 2019 год. Неявных дубликатов нет.

Столбец 'month'¶

In [255]:
# вызываю функцию для проверки на неявные дубликаты
dubl(data, 'month')
Число уникальных значений: 9
Уникальные значения:
 ['Май' 'Август' 'Апрель' 'Июль' 'Февраль' 'Июнь' 'Сентябрь' 'Январь'
 'Март']

В датасете присутствуют данные с января по сентябрь. Здесь также нет неявных дубликатов. Заменю значения месяца на порядковый номер.

In [256]:
# создаю словарь для замены
change_month = {'Январь': 1, 'Февраль': 2, 'Март': 3, 'Апрель': 4,\
                'Май': 5, 'Июнь': 6, 'Июль': 7, 'Август': 8, 'Сентябрь':9}

# меняю значения
data.month = data.month.map(change_month)

Столбец 'company'¶

In [257]:
# вызываю функцию для вывода уникальных значений
dubl(data, 'company')
Число уникальных значений: 40
Уникальные значения:
 ['Mercur Auto' 'Автоцентр-Бавария' 'БИПЭК АВТО' 'Вираж'
 'Eurasia Motor Premium' 'Daewoo Bus Kazakhstan' 'Caspian Motors'
 'Каспиан Моторс' 'СемАЗ' 'Astana Motors' 'Astana Motors '
 'Hyundai Com Trans Kazakhstan ' 'Nissan Manufacturing RUS' 'СВС-ТРАНС'
 'Allur Auto' 'Almaty Motors Premium' 'TERRA MOTORS' 'ТК КАМАЗ'
 'Toyota Motor Kazakhstan' 'Лифан Моторс Рус' 'MAN Truck & Bus Kazakhstan'
 'Автомир ГК' 'Autokapital' 'Автокапитал' 'ММС Рус' 'MMC RUS'
 'Mercur Autos' 'УзАвто-Казахстан' 'Равон Моторс Казахстан'
 'Ravon Motors Kazakstan' 'Renault Россия' 'Scania Central Asia'
 'Subaru Kazakhstan' 'Volkswagen Group Rus' 'Автодом Motors KST'
 'Автомир-Центр' 'ТОО "Eurasia Motor Zhaik"' 'Scandinavian Motors'
 'Хино Моторс Казахстан' 'Hino Motors ']

Выявил следующие дубликаты:

  • 'Astana Motors' и 'Astana Motors '. В одном словосочетании есть пробел в конце, поэтому дубли.
  • 'Autokapital' и 'Автокапитал'.
  • 'Hino Motors ' и 'Хино Моторс Казахстан'. Решил их объединить, потому что это одна компания, которая находится под одним адресом.
  • 'Caspian Motors' и 'Каспиан Моторс'.
  • 'MMC RUS' и 'ММС Рус'.
  • 'Mercur Auto' и 'Mercur Autos'.
  • 'Ravon Motors Kazakstan' и 'Равон Моторс Казахстан'.
  • 'Автомир-Центр' и 'Автомир ГК'. Считаю, 'Автомир-Центр' входит в 'Автомир ГК', поэтому решаю их объединить.

Помимо дублей нашел ошибки:

  • В названии 'Hyundai Com Trans Kazakhstan ' лишний пробел в конце.
  • В названии 'Hino Motors ' в конце есть пробел, который надо убрать.
  • Присутствует грамматическая ошибка в названии 'Ravon Motors Kazakstan'.
In [258]:
# удаляю лишние пробелы
space(data, 'company')
In [259]:
# словарь для замены неявных дубликатов
change_company = {'Автокапитал': 'Autokapital',
                   'Хино Моторс Казахстан': 'Hino Motors',
                   'Каспиан Моторс': 'Caspian Motors',
                   'ММС Рус': 'MMC RUS',
                   'Mercur Autos': 'Mercur Auto',
                   'Равон Моторс Казахстан': 'Ravon Motors Kazakstan',
                   'Автомир ГК': 'Автомир-Центр'}

# вызываю функцию для замены значений
implicit_dubl(data, 'company', change_company)
Количество уникальных значений после очистки: 32

После работы с неявными дубликатами осталось 32 уникальные компании, которые продают автомобили.

Столбец 'brand'¶

In [260]:
# вывожу уникальные значения
dubl(data, 'brand')
Число уникальных значений: 46
Уникальные значения:
 ['Audi' 'BMW' 'Chevrolet' 'Daewoo' 'Ford' 'Foton' 'GAZ' 'Hyundai'
 'Hyundai Truck & Bus' 'Infiniti' 'Isuzu' 'Iveco' 'Jac' 'Jaguar' 'KAMAZ'
 'Kia' 'Lada' 'Land Rover' 'Lexus' 'Lifan' 'MAN' 'Mazda' 'Mercedes-Benz'
 'Mercedes-Benz Trucks' 'Mini' 'Mitsubishi' 'Nissan' 'Peugeot' 'Porsche'
 'Ravon' 'Renault' 'Scania' 'Skoda' 'Subaru' 'Toyota' 'UAZ' 'Volkswagen'
 'Volvo' 'Урал' 'Cadillac' 'Shacman' 'Hino' 'Dong Feng' 'ANKAI' 'Nefaz'
 'Yutong']

Считаю, что 'Mercedes-Benz Trucks' стоит объединить с 'Mercedes-Benz', так они относятся к одному концерну. Также лучше объединить 'Hyundai' c 'Hyundai Truck & Bus', так как 'Hyundai Truck & Bus' - это дочерняя компания и официальный представитель за пределами Южной Кореи. Также надо изменить на одно значение "Dong Feng" на "Dongfeng" (в переводе с китайского "восточный ветер").

In [261]:
# заменяю названия компаний
change_brand = {'Mercedes-Benz Trucks': 'Mercedes-Benz',
                'Hyundai Truck & Bus': 'Hyundai',
                'Dong Feng': 'Dongfeng'}

# вызываю функцию для замены значений
implicit_dubl(data, 'brand', change_brand)
Количество уникальных значений после очистки: 44

В датасете присутствуют 44 различных бренда.

Столбец 'model'¶

In [262]:
# приведу все значения к нижнему регистру
low(data, 'model')

# вувожу уникальные значения
dubl(data, 'model')
Число уникальных значений: 273
Уникальные значения:
 ['a3' 'a4' 'a6' 'a7' 'a8' 'q5' 'q7' '3 серия' '5 серия' '6 серия'
 '7 серия' 'x1' 'x3' 'x4' 'x5' 'x6' 'x7' 'z4' 'aveo' 'captiva' 'niva'
 'tahoe' 'tracker' 'bs 106 a' 'fiesta' 'mondeo' 'bj3253dmpkb-ad' '2217'
 '2310' '2705' '2752' '3221' '3302' '3308' '3309' 'next' 'accent' 'creta'
 'elantra' 'santa fe' 'sonata' 'tq-1/h-1' 'county' 'h350' 'hd 160'
 'hd 170' 'hd 65' 'hd 78' 'universe' 'tucson' 'q50' 'qx50' 'qx60' 'qx80'
 'd-max' 'nmr' 'npr' 'daily' 'daily a' 'daily v' 'stralis' 'tipper' 's3'
 's5' 'f-pace' 'f-type' 'xe' 'xf' 'xj' '4308' '4311' '43118' '43253'
 '43502' '44108' '45142' '45143' '53215' '53504' '53605' '54115' '5490'
 '65111' '65115' '65116' '65117' '6520' '65206' '65225' '66052' '6606'
 'cerato' 'optima' 'picanto' 'quoris' 'rio' 'sorento' 'soul' 'sportage'
 '4x4' 'granta' 'kalina' 'largus' 'vesta' 'xray' 'вис' 'discovery'
 'discovery sport' 'range rover' 'range rover evoque' 'range rover sport'
 'es' 'gx' 'ls' 'lx' 'nx' 'rx' 'rxh' 'murman' 'solano' 'x50' 'x60' 'tg'
 '3' '6' 'cx-5' 'a-class' 'c-class' 'cla-class' 'cls-class' 'e-class'
 'g-class' 'gla-class' 'glc-class' 'gle-class' 'gls-class' 's-class'
 'actros' 'sprinter' 'tourismo' 'v-class' 'vito' 'countryman' 'asx'
 'outlander' 'pajero iv' 'pajero sport' 'almera' 'juke' 'murano' 'qashqai'
 'terrano' 'x-trail' '301' 'partner panel van' '911 carrera s' 'cayenne'
 'macan' 'panamera' 'nexia r3' 'r4' 'duster' 'kaptur' 'koleos' 'logan'
 'master' 'sandero' 'g-series' 'p-series' 'r-series' 'octavia' 'rapid'
 'superb' 'forester' 'legacy' 'outback' 'xv' 'camry' 'corolla'
 'highlander' 'hilux dc' 'lc prado' 'lc200' 'rav4' '2206' '3151' '3303'
 '3741' '3909' '3962' 'cargo' 'patriot' 'patriot pickup' 'amarok'
 'caddy kasten ' 'caddy maxi kombi' 'caravelle' 'jetta' 'multivan' 'polo'
 'tiguan' 'touareg' 'transporter' 'transporter kasten' 'xc90' '4320'
 '4320-1951-40' '4320-1951-60' '5557' 'myway' 'cx-9' 'hd 35'
 'range rover velar' 'bs 106 d' 'kodiaq' 'escalade' 'glc coupe' 'x200'
 'e-pace' 'dokker' 'eclipse cross' 'x70' 'n120' 'n75' 't6' 'sx3258dr384'
 'iev' 's-series' 'nqr' 'x2' 's90' 'xc60' 'c-hr' '300' 'xc40' 'n56' 'xt5'
 '500' 'stinger' 'hatch' 'traverse' 'nms85' 'bj1069vdjea-f1' 'hd 79'
 'bwc6665ga5' 'i8' 'ux' '8 серия' 'cabrio' 'hf-d105' 'nps' 'q8' 'rs5'
 'teramont' '5299' 'i-pace' 's7' '45141' '32551' 'bc 211 ma' 'ex8'
 'bc 095' 'camaro' 'hff6850g' 'transporter kombi ' 'gle coupe'
 'hff6124g03ev3' 'hff6127gz-4' 'setra' 'zk6108hgh' 'arkana' 'v90' '58815z']

У значений 'transporter kombi ' и 'caddy kasten ' есть лишние пробелы, удаляю.

In [263]:
# удаляю лишние пробелы
space(data, 'model')

В Казахстане представлено 273 разные модели.

Столбец 'year_of_release'¶

Проверка на неявные дубликаты¶
In [264]:
# вывожу уникальные значения
dubl(data, 'year_of_release')
Число уникальных значений: 9
Уникальные значения:
 ['2018' '2017' '2019' nan '2016' '2014' '2013' '2011' '2\xa0018'
 '2\xa0019']

В столбце есть пропуски, а также скрытые пробелы ('\xa0') после конвертации из Excel в csv.

In [265]:
# удаление ошибок
change_year_of_release = {r'\xa0': '', 'nan': np.NaN}
implicit_dubl(data, 'year_of_release', change_year_of_release)
Количество уникальных значений после очистки: 7

Далее необходимо заполнить пропуски.

Обработка пропусков¶

Заполню пропуски модой. Если останутся пропуски, значит в этой категории автомобилей данных о годе выпуска нет. В таком случае поищу информацию в сторонних источниках.

In [266]:
# заполняю пропуски модой
change_mode(data, 'brand', 'model', 'year_of_release')
Количество незаполненных строк: 6

Выведу на экран строки с оставшимися пропусками.

In [267]:
data.query('year_of_release.isna()')
Out[267]:
year month company brand model year_of_release producing_country type_of_fuel engine_volume_l transmission type_of_drive client_type quantity price_usd sale_usd area segmentation class_2013
9631 2019 7 Hyundai Com Trans Kazakhstan Hyundai hd 160 NaN Республика Казахстан NaN NaN NaN NaN Юр. Лицо 1.00 34000 34000 г.Алматы Коммерческие автомобили Среднетоннажные грузовики
20223 2019 2 Автомир-Центр Mazda 3 NaN Российская Федерация NaN NaN NaN NaN NaN 1.00 21547,25584 21547,25584 Карагандинская область Легковые автомобили C класс
20224 2019 2 Автомир-Центр Mazda 3 NaN Российская Федерация NaN NaN NaN NaN NaN 1.00 21547,25584 21547,25584 г.Нур-Султан Легковые автомобили C класс
20225 2019 3 Автомир-Центр Mazda 3 NaN Российская Федерация NaN NaN NaN NaN NaN 1.00 21596,81698 21596,81698 Карагандинская область Легковые автомобили C класс
20226 2019 3 Автомир-Центр Mazda 3 NaN Российская Федерация NaN NaN NaN NaN NaN 1.00 21596,81698 21596,81698 г.Нур-Султан Легковые автомобили C класс
20227 2019 3 Автомир-Центр Mazda 3 NaN Российская Федерация NaN NaN NaN NaN NaN 1.00 21596,81698 21596,81698 г.Нур-Султан Легковые автомобили C класс

Осталось 2 вида автомобилей.

  • Среднетоннажный грузовик Hyundai hd 160. О нем достоверной информации в интернете не нашел, поэтому поставлю 0.
  • Mazda 3 Легковой автомобиль С класса, который выпускают с 2003 года по настоящее время. Их всего 5 штук, поэтому их тоже заполню по моде, но по всем значениям автомобилей Mazda.
In [268]:
# заполняю строку со Hyundai hd 160
data.loc[data.model == "hd 160", 'year_of_release'] = 0

# заменяю значение в столбце 'year_of_release' у автомобиля Mazda 3 на моду
data.loc[data.model == "3", 'year_of_release'] = data.query('brand == "Mazda"').year_of_release.mode()[0]
In [269]:
# перевожу значение столбца в тип 'int'
data.year_of_release = data.year_of_release.astype(int)

Столбец 'producing_country'¶

In [270]:
dubl(data, 'producing_country')
Число уникальных значений: 19
Уникальные значения:
 ['Германия' 'США' 'Австрия' 'Республика Казахстан' 'Российская Федерация'
 'Корея' 'Япония' 'Таиланд' 'Китай' 'UK' 'Узбекистан' 'Венгрия' 'Турция'
 'Испания' 'Нидерланды' 'Польша' 'Швеция' 'Белоруссия' 'Бельгия']

В этом столбце проблем нет. В Казахстане продают автомобили из 19-ти стран.

Столбец 'type_of_fuel'¶

In [271]:
# вывожу уникальные значения столбца
dubl(data, 'type_of_fuel')
Число уникальных значений: 10
Уникальные значения:
 ['Бензин' 'Дизель' nan 'дизель ' 'бензин ' 'гибрид' '2' '1,6' '0'
 'Электро' 'Электричество']

Выводы:

  • есть пропуски,
  • есть неявные дубликаты,
  • есть данные, которые похожи на объем двигателя
  • значение "0" может означать пропуск.

С этими ошибками надо разобраться.

Работа с аномальными значениями¶

Для начала выведу строки, в которых в типе двигателя указан его объем.

In [272]:
data.query('type_of_fuel == "2" or type_of_fuel == "1,6"').head(10)
Out[272]:
year month company brand model year_of_release producing_country type_of_fuel engine_volume_l transmission type_of_drive client_type quantity price_usd sale_usd area segmentation class_2013
23723 2019 1 Renault Россия Renault duster 2019 Российская Федерация 2 MT 4WD 4WD NaN 1.00 12249,61125 12249,61125 Западно-Казахстанская область Внедорожники Субкомпактные SUV
23724 2019 1 Renault Россия Renault duster 2018 Российская Федерация 2 AT 4WD 4WD NaN 1.00 12249,61125 12249,61125 г.Нур-Султан Внедорожники Субкомпактные SUV
23725 2019 1 Renault Россия Renault duster 2018 Российская Федерация 2 AT 4WD 4WD NaN 1.00 12249,61125 12249,61125 г.Нур-Султан Внедорожники Субкомпактные SUV
23726 2019 1 Renault Россия Renault duster 2018 Российская Федерация 2 AT 4WD 4WD NaN 1.00 12249,61125 12249,61125 Кызылординская область Внедорожники Субкомпактные SUV
23727 2019 1 Renault Россия Renault duster 2018 Российская Федерация 2 MT 4WD 4WD NaN 1.00 12249,61125 12249,61125 Карагандинская область Внедорожники Субкомпактные SUV
23728 2019 1 Renault Россия Renault duster 2018 Российская Федерация 1,6 MT 4WD 4WD NaN 1.00 12249,61125 12249,61125 Карагандинская область Внедорожники Субкомпактные SUV
23729 2019 1 Renault Россия Renault duster 2018 Российская Федерация 2 AT 4WD 4WD NaN 1.00 12249,61125 12249,61125 Карагандинская область Внедорожники Субкомпактные SUV
23730 2019 1 Renault Россия Renault duster 2018 Российская Федерация 2 AT 4WD 4WD NaN 1.00 12249,61125 12249,61125 Карагандинская область Внедорожники Субкомпактные SUV
23731 2019 1 Renault Россия Renault duster 2018 Российская Федерация 2 MT 4WD 4WD NaN 1.00 12249,61125 12249,61125 Карагандинская область Внедорожники Субкомпактные SUV
23732 2019 1 Renault Россия Renault duster 2018 Российская Федерация 2 MT 4WD 4WD NaN 1.00 12249,61125 12249,61125 Карагандинская область Внедорожники Субкомпактные SUV

Видно, что значения в столбцах 'type_of_fuel', 'engine_volume_l', 'transmission' приняли значения из столбца справа. Чтобы это изменить надо:

  • значения в столбце 'type_of_fuel' перенести в столбец 'engine_volume_l'
  • из 'engine_volume_l в 'transmission'
  • в столбце 'type_of_fuel' после замены появятся пропуски, которые потом можно будет заполнить исходя из марки автомобиля.
In [273]:
# создаю датасет отфильтрованный по значению топлива в столбце 'type_of_fuel'
to_change = data.query('type_of_fuel in ["2", "1,6"]')

# замена значений в столбцах 'type_of_fuel', 'engine_volume_l' и 'transmission'
to_change['transmission'] = to_change['engine_volume_l']
to_change['engine_volume_l'] = to_change['type_of_fuel']
to_change['type_of_fuel'] = np.NaN
In [274]:
# объединяю датасеты to_change и data
data = pd.concat([data, to_change])

# удаляю строки в которых в столбце 'type_of_fuel' есть литраж.
data = data.query('type_of_fuel not in ["2", "1,6"]').reset_index(drop=True)

Значения "0" заменю на "NaN" в следующем разделе.

Работа с неявными дубликатами¶

Чтобы убрать неявные дубликаты надо привести все к одному регистру (переведу в нижний), удалить пробелы и заменить строки со значением "электро" на "электричество".

In [275]:
# удаляю лишние пробелы
space(data, 'type_of_fuel')

# привожу к нижнему регистру
low(data, 'type_of_fuel')

# заменяю значения
change_type_of_fuel = {'0': np.NaN, 'электро': 'электричество'}
implicit_dubl(data, 'type_of_fuel', change_type_of_fuel)
Количество уникальных значений после очистки: 4
In [276]:
data.type_of_fuel.unique()
Out[276]:
array(['бензин', 'дизель', nan, 'гибрид', 'электричество'], dtype=object)
Заполнение пропусков¶

Столбец "type_of_fuel" содержит категриальные значения. Эти пропуски можно заполнить по моде также, как и в п. 4.4.6.2. Но чтобы это сделать надо сгруппировать данные по столбцам 'brand' и 'model'.

In [277]:
# заполняю пропуски модой
change_mode(data, 'brand', 'model', 'type_of_fuel')
Количество незаполненных строк: 899

Выведу на экран модели с оставшимися пропусками.

In [278]:
# проверю остались ли пропуски
data.query('type_of_fuel.isna()').groupby(['brand', 'model']).agg({'type_of_fuel': 'size'}).head()
Out[278]:
type_of_fuel
brand model
ANKAI hf-d105 30
Dongfeng bwc6665ga5 10
Foton bj1069vdjea-f1 19
bj3253dmpkb-ad 9
Hyundai ex8 6

Эти пропуски заполню вручную. Информацию найду в интернете. По типам двигателя модели разделяются так:

  • Бензиновые двигатели: Mazda 3, Mazda 6, Mazda cx-5, Mazda cx-9, Renault arkana, Renault kaptur, Renault logan.
  • Дизельные: ANKAI hf-d105, Dongfeng bwc6665ga5, Foton bj1069vdjea-f1, Foton bj3253dmpkb-ad, Hyundai ex8, Iveco daily a, Iveco daily v, Iveco stralis, Iveco tipper, Renault koleos, Renault master, Shacman sx3258dr384, все Уралы.

Автомобилей Renault dokker выпускали и с дизельным и с бензиновым двигателями, поэтому для них поставлю значение "unknown".

In [279]:
# заполняю пропуски с дизельными автомобилями
disel_car = ['hf-d105', 'bwc6665ga5', 'bj1069vdjea-f1', 'bj3253dmpkb-ad', 'ex8', 'daily a', 'daily v'\
             , 'stralis', 'tipper', 'koleos', 'master', 'sx3258dr384', '32551', '4320', '4320-1951-40',\
            '4320-1951-60', '5557', 'hd 160']
change_isna(data, 'model', 'type_of_fuel', disel_car, 'дизель')

# заполню пропуски с бензиновыми автомобилями
petrol_car = ['3', '6', 'cx-5', 'cx-9', 'arkana', 'kaptur', 'logan']
change_isna(data, 'model', 'type_of_fuel', petrol_car, 'бензин')

# заполняю строки с Renault dokker случайными значениями
type_f = ['бензин', 'дизель']  # создаю список типов двигателей для замены
data.loc[(data.type_of_fuel.isna()) & (data.model == "dokker"), 'type_of_fuel'] = "unknown"


# проверяю на наличие пропусков
data.type_of_fuel.isna().sum()
Out[279]:
0
In [280]:
# еще раз выведу уникальные значения типов топлива в двигателях
dubl(data, 'type_of_fuel')
Число уникальных значений: 5
Уникальные значения:
 ['бензин' 'дизель' 'гибрид' 'unknown' 'электричество']

В Казахстане продают автомобили с 4 типами двигателей: бензиновые, дизельные, гибридные и электрические. Значение "unknown" для автомобилей, у которых не удалось достоверно точно определить тип двигателя.

Столбец 'engine_volume_l'¶

In [281]:
# вывожу уникальные значения столбца
dubl(data, 'engine_volume_l')
Число уникальных значений: 111
Уникальные значения:
 ['1,4' '2' '3' '4,4' '6,6' '1,5' '1,596' '2,4' '1,7' '26,7' '25,7' '24,7'
 '23,7' '22,7' '21,7' '20,7' '19,7' '18,7' '17,7' '16,7' '15,7' '14,7'
 '13,7' '12,7' '11,7' '10,7' '9,7' '8,7' '7,7' '6,7' '5,7' '4,7' '3,7'
 '2,7' '6,2' '1,4 Turbo' '2,5' '1,6' nan '2,8' '4,3' '4,3,' '1,6 MPI'
 '2,0 MPI' '2,4 GDI' '2,4 MPI' '2,5 CRDI VGT' '2,5 CRDI WGT' '3,9' '7,5'
 '12,3' '1,6 T-GDI' '2,0 CRDI' '2.0' '3,5' '5,6' '5,2' '3,0 L' '1.6' '1.5'
 '5' '1,598' '1,248' '1,998' '2,359' '1,999' '3,342' '1,591' '3,47' '1,69'
 '1,774' '2.5' '2.7 ' '3.5' '2.7' '4.6' '4,6' '5.7' '1,8' '10,5' '4' '5,5'
 '12' '12,8' '11' '2,2' '1,2' '1,485' '13' '1,33' '#Н/Д' '4.0' '2.4G'
 '2,4G' '2.8' '2,693' '2,0' '3,8' '3.8' '1,2T' '3,6' '7,6' '4,9' '2.0h'
 '2,9' '400 Л.С.' '4,98 L,' '4,98' '88 KWH' '8,4 L,' '6,7L' '6,5']

В этом столбце есть следующие проблемы:

  • Вместо объема двигателя есть значения мощности 88 kwh и 400 л.с.
  • Надо заменить запятые на точки.
  • Есть аномальные значения объема двигателя, которые более 10,7 литров.
  • Наличие букв наряду с цифрами.

Начну с отработки аномальных значений.

Комментарий ревьюера:

kwh это тоже мощность

Работа с аномальными значениями¶
In [282]:
# привожу к нижнему регистру
low(data, 'engine_volume_l')

# проверю, что за техника у которой указаны лошадиные силы и килловаты в час
data.query('engine_volume_l in ["400 л.с.", "88 kwh"]').head()
Out[282]:
year month company brand model year_of_release producing_country type_of_fuel engine_volume_l transmission type_of_drive client_type quantity price_usd sale_usd area segmentation class_2013
39507 2019 2 Almaty Motors Premium Jaguar i-pace 2019 UK электричество 400 л.с. РЕДУКТОР 4WD Физ. Лицо 1.00 117236,9729 117236,9729 г.Алматы Внедорожники Компактные SUV
39508 2019 4 Almaty Motors Premium Jaguar i-pace 2019 UK электричество 400 л.с. РЕДУКТОР 4WD Физ. Лицо 1.00 105388,7924 105388,7924 г.Алматы Внедорожники Компактные SUV
39509 2019 6 Almaty Motors Premium Jaguar i-pace 2019 UK электричество 400 л.с. РЕДУКТОР 4WD Юр. Лицо 1.00 120413,2793 120413,2793 г.Алматы Внедорожники Компактные SUV
39781 2019 7 Allur Auto ANKAI hff6124g03ev3 2019 Республика Казахстан электричество 88 kwh NaN 2WD Юр. Лицо 1.00 307986,3633 307986,3633 Костанайская область Коммерческие автомобили Большие автобусы
39782 2019 7 Allur Auto ANKAI hff6124g03ev3 2019 Республика Казахстан электричество 88 kwh NaN 2WD Юр. Лицо 1.00 307986,3633 307986,3633 Костанайская область Коммерческие автомобили Большие автобусы

Из сторонних источников выяснил:

  • ankai hff6124g03ev3 - это электробус, поэтому киловатты в час заменю на значение 0.
  • у автомобиля jaguar i-pace с 400 л. с. имеет электрический двигатель (нашел в сторонних источниках), поэтому и у него поставлю значение 0.
In [283]:
# заменю все запятые на точки и заменяю значения 400 л.с. и 88 kwh
change_engine_volume_l = {',': '.', '400 л.с.': '0', '88 kwh': '0', r'[^\d.]': ''}
implicit_dubl(data, 'engine_volume_l', change_engine_volume_l)

# заменяю '' на np.NaN
data.engine_volume_l = data.engine_volume_l.replace('', np.NaN, regex=True)

# удаляю лишние точки вконце значений
data.engine_volume_l = data.engine_volume_l.str.strip('.')
dubl(data, 'engine_volume_l')
Количество уникальных значений после очистки: 85
Число уникальных значений: 82
Уникальные значения:
 ['1.4' '2' '3' '4.4' '6.6' '1.5' '1.596' '2.4' '1.7' '26.7' '25.7' '24.7'
 '23.7' '22.7' '21.7' '20.7' '19.7' '18.7' '17.7' '16.7' '15.7' '14.7'
 '13.7' '12.7' '11.7' '10.7' '9.7' '8.7' '7.7' '6.7' '5.7' '4.7' '3.7'
 '2.7' '6.2' '2.5' '1.6' nan '2.8' '4.3' '2.0' '3.9' '7.5' '12.3' '3.5'
 '5.6' '5.2' '3.0' '5' '1.598' '1.248' '1.998' '2.359' '1.999' '3.342'
 '1.591' '3.47' '1.69' '1.774' '4.6' '1.8' '10.5' '4' '5.5' '12' '12.8'
 '11' '2.2' '1.2' '1.485' '13' '1.33' '4.0' '2.693' '3.8' '3.6' '7.6'
 '4.9' '2.9' '0' '4.98' '8.4' '6.5']

После удаления буквенных символов переведу столбец в тип float.

In [284]:
# переведу значения столбца в тип float
data.engine_volume_l = data.engine_volume_l.astype(float)
In [285]:
# выведу на экран технику, у которой объем двигателя более 10 литров
data.query('engine_volume_l > 10').groupby(['brand', 'model', 'engine_volume_l']).size().head()
Out[285]:
brand      model  engine_volume_l
Chevrolet  niva   10.70              1
                  11.70              1
                  12.70              1
                  13.70              1
                  14.70              1
dtype: int64

Исходя из сторонних источников делаю следующие выводы:

  1. У Mercedes-Benz actros объем двигателя от 10,677 л до 15,9 л. В датасете у него значения варьируются от 11 л до 12,8 л. Эти значения в пределах допуска, их менять не надо.
  2. Scania g-series, p-series, r-series у этих типов автомобилей есть двигатели с объемом 13 л. Эти значения в порядке.
  3. Автомобили Chevrolet Niva в 2019 году выпускали с объемом двигателя 1,7. А здесь много высоких значений - не порядок.
  4. MAN tg в датасете имеет объем двигатель 10,5 л, с таким объемом их выпускали.
  5. Mercedes-Benz tourismo - это автобус, который делали с двигателями объемом 12 л, как и в датасете.
  6. Автобус Hyundai universe имеет объем двигателя 12,3 л. В данных значения совпадают.

Кроме Chevrolet Niva выбивающихся значений не увидел, рассмотрю этот автомобиль подробнее.

In [286]:
# отфильтрую датасет по модели автомобиля Niva и сгруппирую по объему двигателя
data.query('model in ["niva"]').groupby(by='engine_volume_l').size().head()
Out[286]:
engine_volume_l
1.70    542
2.70      1
3.70      1
4.70      1
5.70      1
dtype: int64

Вижу, что большинство автомобилей Нива с двигателем 1,7 л, остальные представлены с разными объемами в одном экземпляре. Это точно ошибка. Все ошибочные значения заменю на объем 1,7.

In [287]:
# заменяю значения для автомобиля 'niva'
data.loc[data.model == 'niva', 'engine_volume_l'] = 1.7
In [288]:
# снова вывожу посмотреть уникальные значения
dubl(data, 'engine_volume_l')
Число уникальных значений: 58
Уникальные значения:
 [ 1.4    2.     3.     4.4    6.6    1.5    1.596  2.4    1.7    6.2
  2.5    1.6      nan  2.7    2.8    4.3    3.9    7.5   12.3    3.5
  5.6    8.7    5.2    5.     1.598  1.248  1.998  2.359  1.999  3.342
  1.591  3.47   1.69   1.774  4.6    5.7    1.8   10.5    4.     5.5
 12.    12.8   11.     2.2    1.2    1.485 13.     1.33   2.693  3.8
  3.6    7.6    4.9    2.9    0.     4.98   8.4    6.7    6.5  ]

Теперь проверю строки с объемом двигателя равным 0.

In [289]:
data.query('engine_volume_l == 0').groupby(by=['brand', 'model', 'type_of_fuel']).size().shape[0]
Out[289]:
2

Это значит, что всего две модели имеют значение 0. Для них значение "0" устанавливал сам, других таких нет. Поэтому проверять это значение не надо. Далее буду заполнять пропуски.

Заполнение пропусков¶

Для начала заменю пропуски у автомобилей с электрическим двигателем. Такие пропуски можно будет заполнить значением 0, поскольку там нет цилиндров, по которым можно посчитать рабочий объем.

In [290]:
data.loc[data.type_of_fuel  == 'электричество', 'engine_volume_l'] = 0

Теперь буду работать с оставшимися пропусками.

In [291]:
# составляю таблицу с пропусками по моделям
engine_vol_isna = (data
                   .query('engine_volume_l.isna()')
                   .groupby(by=['brand', 'model'])['engine_volume_l']
                   .size()
                   .reset_index()
                  )
In [292]:
# таблица без пропусков по моделям, которые есть в таблице engine_vol_isna
engine_vol_notna = data.query('model in @engine_vol_isna.model and engine_volume_l.notna()')\
.groupby(by=['brand', 'model'])['engine_volume_l'].size().reset_index()

# объединю таблицы engine_vol_notna и engine_vol_isna, чтобы посмотреть можно ли какие-то значения заменить модой
engine_vol_isna = (engine_vol_isna
                   .merge(engine_vol_notna, on=['brand', 'model'], how='left', suffixes=('_isna_count', '_notna_count'))
)

engine_vol_isna.sort_values('engine_volume_l_notna_count', ascending=False).head()
Out[292]:
brand model engine_volume_l_isna_count engine_volume_l_notna_count
79 Ravon nexia r3 361 1338.00
34 Jac s3 175 1111.00
90 Toyota corolla 2 1087.00
9 GAZ 3302 127 871.00
91 Toyota hilux dc 2 825.00

Помимо значений из таблицы, из сторонних источников выяснил, что у части автомобилей устанавливали только один тип двигателя. Исходя из полученных данных установил, что следующие значения можно заполнить модой:

  • Ravon nexia r3,
  • Jac s3,
  • Toyota corolla,
  • GAZ 3302,
  • Toyota hilux dc,
  • Volkswagen polo,
  • GAZ next,
  • Jac s5,
  • ANKAI hff6850g,
  • GAZ 2705,
  • Jac n120,
  • Ravon r4,
  • GAZ 3221,
  • Jac t6,
  • GAZ 3308,
  • GAZ 2752,
  • Jac n56,
  • Volvo xc60,
  • Land Rover range rover velar,
  • Volvo xc90,
  • Volkswagen caravelle,
  • Isuzu npr,
  • Peugeot 301.
In [293]:
# создам список с моделями авто, которые буду заполнять модой
list_car = ['nexia r3',
            's3',
            'corolla',
            '3302',
            'hilux dc',
            'polo',
            'next',
            's5',
            'hff6850g',
            '2705',
            'n120',
            'r4',
            '3221',
            't6',
            '3308',
            '2752',
            'n56',
            'xc60',
            'range rover velar',
            'xc90',
            'caravelle',
            'npr',
            '301']

# заполняю пропуски модой и сохраняю все изменения в исходную переменную data
data_copy = data.query('model in @list_car')
change_mode(data_copy, 'brand', 'model', 'engine_volume_l')

# убираю строки в исходном датасете, которые заменял в копии
data = data.query('model not in @data_copy.model')

# добавляю в исходный датасет измененные строки
data = pd.concat([data, data_copy]).reset_index(drop=True)

# проверю, не потерял ли строки и столбцы после соединения
data.shape
Количество незаполненных строк: 0
Out[293]:
(39966, 18)

При замене пропусков данные не потерял. Далее выведу на экран технику, которую не удалось заполнить модой.

In [294]:
(data
 .query('engine_volume_l.isna()')
 .groupby(['brand', 'model'])['engine_volume_l']
 .size()
 .reset_index()
 .sort_values(by='engine_volume_l', ascending=False)
 .head()
)
Out[294]:
brand model engine_volume_l
66 Renault duster 650
71 Renault sandero 435
69 Renault logan 320
43 KAMAZ 65115 259
67 Renault kaptur 230

Заполню пропуски для следующих моделей вручную на основании данных, полученных в сторонних источниках:

  • 1.6 л: Renault sandero, Renault logan, Peugeot partner panel van;
  • 2.0 л: Jac x200, Jaguar e-pace, Land Rover discovery sport, Volkswagen transporter kasten;
  • 2.3 л: Renault master;
  • 2.5 л: Hyundai h350, Mazda cx-9, Hyundai hd 35
  • 2.98 л: Dongfeng bwc6665ga5
  • 3.5 л: Isuzu nms85
  • 3.8 л: Foton bj1069vdjea-f1, Jac n75,
  • 3.9 л: Hyundai hd 78, Hyundai hd 65,
  • 5.2 л: Isuzu nps, Isuzu nqr
  • 6.7 л: KAMAZ 45143, KAMAZ 65116, KAMAZ 65117, KAMAZ 43253, KAMAZ 43502, KAMAZ 53605, KAMAZ 4308
  • 7.545 л: Hyundai hd 160
  • 10.7 л: Mercedes-Benz setra
  • 10.85 л: KAMAZ 45142, KAMAZ 54115, KAMAZ 45141, KAMAZ 53215
  • 11.15 л: Урал 4320-1951-60, Урал 4320-1951-40, Урал 32551
  • 11.76 л: KAMAZ 65111, KAMAZ 53504, KAMAZ 65225,
  • 12.7 л: Hyundai universe.

Для остальных моделей поставлю значение unknown, потому что у этих типов автомобилей несколько видов двигателей с разным объемом.

Также определил, что у автобусов Daewoo неверно указан объем двигателя - 2.5, а должно быть 7.64.

In [295]:
# изменю значения для автобусов Daewoo
data.loc[data.brand == "Daewoo", 'engine_volume_l'] = 7.64
In [296]:
# функция для заполнения пропусков в столбцах по списку
def change_isna(df, cut_by_column, column, lis, replace_by):
    df.loc[data[cut_by_column].isin(lis), column] = replace_by
In [297]:
# составляю список авто с обемом двигателя 1.6 и заполняю пропуски
list_1_6 = ['sandero', 'logan', 'partner panel van']
change_isna(data, 'model', 'engine_volume_l', list_1_6,  1.6)

# составляю список авто с обемом двигателя 2.0 и заполняю пропуски
list_2 = ['x200', 'e-pace', 'discovery sport', 'transporter kasten']
change_isna(data, 'model', 'engine_volume_l', list_2,  2.0)

# составляю список авто с обемом двигателя 2.3 и заполняю пропуски
list_2_3 = ['master']
change_isna(data, 'model', 'engine_volume_l', list_2_3,  2.3)

# составляю список авто с обемом двигателя 2.3 и заполняю пропуски
list_2_5 = ['h350', 'cx-9', 'hd 35']
change_isna(data, 'model', 'engine_volume_l', list_2_5,  2.5)

# составляю список авто с обемом двигателя 2.98 и заполняю пропуски
list_2_98 = ['bwc6665ga5']
change_isna(data,  'model', 'engine_volume_l', list_2_98, 2.98)

# составляю список авто с обемом двигателя 3.5 и заполняю пропуски
list_3_5 = ['nms85']
change_isna(data, 'model', 'engine_volume_l', list_3_5, 3.5)

# составляю список авто с обемом двигателя 3.8 и заполняю пропуски
list_3_8 = ['bj1069vdjea-f1', 'n75']
change_isna(data, 'model', 'engine_volume_l', list_3_8, 3.8)

# составляю список авто с обемом двигателя 3.9 и заполняю пропуски
list_3_9 = ['hd 78', 'hd 65']
change_isna(data, 'model', 'engine_volume_l', list_3_9, 3.9)

# составляю список авто с обемом двигателя 5.2 и заполняю пропуски
list_5_2 = ['nps', 'nqr']
change_isna(data,  'model', 'engine_volume_l', list_5_2, 5.2)

# составляю список авто с обемом двигателя 6.7 и заполняю пропуски
list_6_7 = ['45143', '65116', '65117', '43253', '43502', '53605', '4308']
change_isna(data, 'model', 'engine_volume_l', list_6_7, 6.7)

# составляю список авто с обемом двигателя 7.545 и заполняю пропуски
list_7_545 = ['hd 160']
change_isna(data, 'model', 'engine_volume_l', list_7_545, 7.545)

# заменяю у автобусов  7.6 и заменяю значения
list_7_6 = ['bs 106 d', 'bc 211 ma', 'bc 095', 'bs 106 a']
change_isna(data, 'model', 'engine_volume_l', list_7_6, 7.6)

# составляю список авто с обемом двигателя 10.7 и заполняю пропуски
list_10_7 = ['setra']
change_isna(data, 'model', 'engine_volume_l', list_10_7, 10.7)

# составляю список авто с обемом двигателя 10.85 и заполняю пропуски
list_10_85 = ['45142', '54115', '45141', '53215']
change_isna(data, 'model', 'engine_volume_l', list_10_85, 10.85)

# составляю список авто с обемом двигателя 11.15 и заполняю пропуски
list_11_15 = ['4320-1951-60', '4320-1951-40', '32551']
change_isna(data, 'model', 'engine_volume_l', list_11_15, 11.15)

# составляю список авто с обемом двигателя 11.76 и заполняю пропуски
list_11_76 = ['65111', '53504', '65225']
change_isna(data, 'model', 'engine_volume_l', list_11_76, 11.76)

# составляю список авто с обемом двигателя 12.7 и заполняю пропуски
list_12_7 = ['universe']
change_isna(data, 'model', 'engine_volume_l', list_12_7, 12.7)

print('Количество оставшихся пропусков:', len(data.query('engine_volume_l.isna()')))
Количество оставшихся пропусков: 1962

Оставшиеся пропуски оставлю незаполненными.

Комментарий ревьюера:

тут лучше НаН оставить, чтобы статистика столбца не изменить (он числовой)

Столбец 'transmission'¶

In [298]:
# выведу уникальные значения столбца
dubl(data, 'transmission')
Число уникальных значений: 81
Уникальные значения:
 ['S-tronic' 'S-Tronic' 'Tiptronic' 'АКПП' 'АКП' 'Steptronic' 'AT' '6 АТ'
 '5 МТ' '6АТ' 'MT' '8AT' '5МТ' 'Powershift S6' '6AT' nan 'Мех.' '6 AT'
 '6 MT' '6MT' '5 AT' '5AT' 'МКПП' '7 DCT' '7DCT' '8 AT' 'МТ' '4 АТ' '6 МТ'
 '6МТ' '4АТ' '4AT' '8АТ' '8 АТ' '5MT' 'AMT' 'CVT (вариатор)'
 'CVT (вариATор)' 'CVT(вAриATор)' 'CVT' '7G-TRONIC' '9G-TRONIC' '12AT'
 '12АТ' '5АТ' '7АТ' '7AT' '6M/T' 'PDK' '8-ступ АКПП' '8' '8АКПП'
 '8 АКПП Tiptronic S' '7 АКПП (PDK)' '7 АКПП PDK' '8 АКПП (PDK)' '7 DSG'
 '7DSG' '6 DSG' '6DSG' 'DSG' '6A' '6А' ' 7 АКП' 'АТ' 'A/T' '7АКП' 'А/T'
 '8A' '9AT' 'РЕДУКТОР' 'DCT' '0' 'АT' 'МT' '#Н/Д' '5M' 'M/T' 'МКП' '5М'
 'М/T' 'TDI']

Теперь переведу все значения в нижний регистр, заменю кириллицу на латиницу и уберу все пробелы в значениях.

In [299]:
# переведу все значения в нижний регистр
low(data, 'transmission')

# заменяю кириллицу на латиницу в столбце
data.transmission = data['transmission'].transform(lambda x: translit(str(x), 'ru', reversed=True))

# удаляю пробелы в значениях
space_del_all(data, 'transmission')

Теперь сделаю функцию для замены значений с помощью регулярных выражений.

In [300]:
# изменяю на значение 'вариатор'
pattern_var = ['cvt\S+', 'cvt']
re_sub(pattern_var, data, 'transmission', 'вариатор')

# изменяю на значение 'робот'
pattern_robot = ['s-tronic', '\d+dsg', 'dsg', '\d+dct', 'dct', 'powershifts6', 'pdk']
re_sub(pattern_robot, data, 'transmission', 'робот')

# изменяю на значение 'автомат'
pattern_acpp = ['\d+akpp\S+','\S+akpp', 'akpp', '\S+akp', 'akp', '\S+at', 'at', 'a\St', '\da', '8', '\S+tronic']
re_sub(pattern_acpp, data, 'transmission', 'автомат')

# изменяю на значение 'механика'
pattern_mech = ['reduktor', 'mkpp', 'mkp', 'tdi', '\Smt', '5m', '\Sm/t', 'm/t', 'mt', 'meh.']
re_sub(pattern_mech, data, 'transmission', 'механика')

# заполняю оставшиеся значения np.NaN
change_transmission = {'0': np.NaN, 'nan': np.NaN, '#n/d': np.NaN}
implicit_dubl(data, 'transmission', change_transmission)
Количество уникальных значений после очистки: 4
Заполнение пропусков¶

Сведу в одну таблицу число пропусков и наличие значений по каждой модели.

In [301]:
# создаю таблицу с числом пропусков
transmission_isna = (data
                     .query('transmission.isna()')
                     .groupby(by=['brand', 'model'])['transmission']
                     .size()
                     .reset_index()
                  )
In [302]:
# таблица без пропусков по моделям, которые есть в таблице transmission_isna
transmission_notna = data.query('model in @transmission_isna.model and transmission.notna()')\
.groupby(by=['brand', 'model'])['transmission'].size().reset_index()

# свожу таблицы transmission_notna и transmission_isna в одну, чтобы посмотреть можно ли какие-то значения заменить модой
transmission_isna = (transmission_isna
                   .merge(transmission_notna, on=['brand', 'model'], how='left', suffixes=('_isna_count', '_notna_count'))
)

transmission_isna.sort_values(by='transmission_notna_count', ascending=False).head()
Out[302]:
brand model transmission_isna_count transmission_notna_count
48 Ravon nexia r3 361 1338.00
27 Jac s3 175 1111.00
63 Toyota corolla 2 1087.00
64 Toyota hilux dc 2 825.00
67 Volkswagen polo 28 516.00

Помимо значений из таблицы, из сторонних источников выяснил, что у части автомобилей устанавливали только один тип двигателя. Исходя из полученных данных сохраню в переменную unknown_tr модели, которые правильно не заполнить. Для них вместо пропусков поставлю значение unknown.

In [303]:
unknown_tr = ['s5',
              'tiguan',
              'duster',
              'amarok',
              'daily',
              'sandero',
              'hf-d105',
              'hff6124g03ev3',
              'd-max',
              'daily a',
              '3',
              '6',
              'cx-5',
              'setra',
              'arkana',
              'kaptur',
              'logan',
              'g-series',
              'p-series',
              'r-series',
              's-series']
In [304]:
# заполняю пропуски значением 'unknown' для моделей из списка unknown_tr
change_isna(data, 'model', 'transmission', unknown_tr, 'unknown')

Есть один автомобиль, на который устанавливали вариатор. Его внесу в список var_tr и заполню для него пропуск значением 'вариатор'.

In [305]:
var_tr = ['koleos']
change_isna(data, 'model', 'transmission', var_tr, 'вариатор')

Теперь заполню пропуски для машин с коробкой автомат.

In [306]:
auto_tr = ['cx-9', 'iev']
change_isna(data, 'model', 'transmission', auto_tr, 'автомат')

Далее создам список с моделями, пропуски которых заполню значением "механика".

In [307]:
mech_tr = ['hff6850g',
           'n120',
           'bwc6665ga5',
           'bj1069vdjea-f1',
           'bj3253dmpkb-ad',
           'ex8',
           'hd 160',
           'nmr',
           'nms85',
           'npr',
           'nps',
           'nqr',
           'daily v',
           'tipper',
           'tg',
           'dokker',
           'master',
           'sx3258dr384',
           '32551',
           '4320',
           '4320-1951-40',
           '4320-1951-60',
           '5557']
change_isna(data, 'model', 'transmission', mech_tr, 'механика')
In [308]:
# заполняю оставшиеся пропуски медианным значением
change_mode(data, 'brand', 'model', 'transmission')
Количество незаполненных строк: 0
In [309]:
# оставшиеся пропуски заполняю значением 'unknown'
data.transmission.unique()
Out[309]:
array(['робот', 'автомат', 'механика', 'unknown', 'вариатор'],
      dtype=object)

Пропусков в столбце нет - порядок.

Столбец 'type_of_drive'¶

In [310]:
# переведу все значения в нижний регистр
low(data, 'type_of_drive')

# удаляю пробелы в значениях
data.type_of_drive = data.type_of_drive.replace(r' ', '', regex=True)

# заменяю кириллицу на латиницу в столбце
data.type_of_drive = data['type_of_drive'].transform(lambda x: translit(str(x), 'ru', reversed=True))

# выведу уникальне значения
dubl(data, 'type_of_drive')
Число уникальных значений: 21
Уникальные значения:
 ['perednij' 'fwd' 'quattro' 'zadnij' 'polnyj' 'awd' '4wd' '4h2.2' 'nan'
 'rwd' '2wd' '4x4' '4h4' '4x2' '4h2' 'astana' '0' 'ff' '4motion'
 'perednij(ff)' '#n/d']

Есть неявные дубликаты, пропуски и аномалии.

Удаление аномалий¶

Аномалии в столбце следующие:

  • 'astana'
  • '#n/d', '0', 'nan' - заменю на pd.NaN, так как это похоже на пропуски.

Сначала посмотрю, на строки со значением 'astana'.

In [311]:
data.query('type_of_drive == "astana"')
Out[311]:
year month company brand model year_of_release producing_country type_of_fuel engine_volume_l transmission type_of_drive client_type quantity price_usd sale_usd area segmentation class_2013
12259 2019 5 ТК КАМАЗ KAMAZ 65116 2019 Республика Казахстан дизель 6.70 механика astana Юр. Лицо 1.00 55655,86316 55655,86316 г.Нур-Султан Коммерческие автомобили Крупнотоннажные грузовики
31419 2019 5 ТК КАМАЗ KAMAZ 45141 2019 Республика Казахстан дизель 10.85 механика astana Юр. Лицо 1.00 53963,81966 53963,81966 г.Нур-Султан Коммерческие автомобили Крупнотоннажные грузовики

Эти значения в двух строках. Для KAMAZ 65116 в любой комплектации характерен задний (FR) тип привода. Для KAMAZ 45141 - полный привод (4WD). Заполню значения для этой техники.

In [312]:
data.loc[data.model == '65116', 'type_of_drive'] = 'FR'
data.loc[data.model == '45141', 'type_of_drive'] = '4WD'
In [313]:
# заменю '#n/d' и '0' на pd.NaN
data.loc[(data.type_of_drive == '#n/d') |\
         (data.type_of_drive == '0') |\
         (data.type_of_drive == 'nan'), 'type_of_drive'] = np.NaN
Работа с явными дубликатами¶

Значения '4x2', '4h2' неявные дубликаты. Они означают, что автомобиль может оснащаться либо передним, либо задним типом привода. Узнаю про эту технику подробнее, чтобы получить точную информацию. Также сразу проверю значение '4h2.2'.

In [314]:
data.query('type_of_drive in ["4x2", "4h2", "4h2.2"]').sort_values('type_of_drive').head()
Out[314]:
year month company brand model year_of_release producing_country type_of_fuel engine_volume_l transmission type_of_drive client_type quantity price_usd sale_usd area segmentation class_2013
11462 2019 9 СВС-ТРАНС Isuzu nmr 2019 Российская Федерация дизель NaN механика 4h2 NaN 1.00 29194,21386 29194,21386 г.Алматы Коммерческие автомобили Малотоннажные грузовики
11452 2019 9 СВС-ТРАНС Isuzu nmr 2019 Российская Федерация дизель NaN механика 4h2 NaN 1.00 29194,21386 29194,21386 г.Алматы Коммерческие автомобили Малотоннажные грузовики
11453 2019 9 СВС-ТРАНС Isuzu nmr 2019 Российская Федерация дизель NaN механика 4h2 NaN 1.00 29194,21386 29194,21386 г.Алматы Коммерческие автомобили Малотоннажные грузовики
11454 2019 9 СВС-ТРАНС Isuzu nmr 2019 Российская Федерация дизель NaN механика 4h2 NaN 1.00 29194,21386 29194,21386 г.Алматы Коммерческие автомобили Малотоннажные грузовики
11455 2019 9 СВС-ТРАНС Isuzu nmr 2019 Российская Федерация дизель NaN механика 4h2 NaN 1.00 29194,21386 29194,21386 г.Алматы Коммерческие автомобили Малотоннажные грузовики

Значения '4h2' принадлежат автомоблию Isuzu nmr, а '4x2' - это Isuzu nqr, Mercedes-Benz actros, Isuzu nmr, Isuzu npr. Значения '4h2.2' имеют: Daewoo bs 106 d, Daewoo bc 211 ma, Daewoo bc 095, Daewoo bs 106 a.

Из сторонних источников выяснил, что вся эта техника имеет задний привод (FR). Можно заменить. Также обнаружил, что у моделей 'pajero iv', 'cayenne', 'macan' ошибочно установлено значение '2wd'. Их надо поменять на '4WD'.

In [79]:
dubl(data, 'type_of_drive')
Число уникальных значений: 19
Уникальные значения:
 ['perednij' 'fwd' 'quattro' 'zadnij' 'polnyj' 'awd' '4wd' '4h2.2' nan
 'rwd' '2wd' '4x4' '4h4' '4x2' '4h2' 'FR' 'ff' '4motion' '4WD'
 'perednij(ff)']
In [80]:
# функция для замены неявных дубликатов
def type_drive(cell):
    if cell in ['perednij', 'fwd', 'ff', 'perednij(ff)']:
        return "FF"
    elif cell in ['zadnij', '4h2', '4x2', '4h2.2', 'rwd']:
        return "FR"
    elif cell in ['quattro', 'polnyj', '4wd', '4motion', '4x4', '4h4']:
        return "4WD"
    elif cell in ['awd']:
        return 'AWD'
    else:
        return cell
In [81]:
# вызываю функцию для замены неявных дубликатов
data.type_of_drive = data.type_of_drive.apply(type_drive)

Теперь надо заменить значения "2wd" на "FF" или "FR". Для этого создам список для переднеприводных автомобилей.

In [82]:
# список с переднеприводных автомобилей
list_2wd_ff = ['accent',
               'creta',
               'elantra',
               'sonata',
               'tucson',
               's3',
               's5',
               's7',
               'asx',
               'eclipse cross',
               'outlander',
               'qashqai',
               'x-trail',
               '301',
               'partner panel van',
               'nexia r3',
               'r4',
               'corolla',
               'caddy kasten',
               'caddy maxi kombi',
               'polo',
               'tiguan',
               's90'
]

# замена значений '2wd' на 'FF'
data.loc[(data['model'].isin(list_2wd_ff)) & (data['type_of_drive'] == "2wd"), 'type_of_drive'] = 'FF'

В датасете обнуражил, что у 'pajero iv', 'cayenne', 'macan' указан тип привода '2wd', а должен быть '4WD'. Это надо исправить.

In [83]:
# заменяю ошибочные '2wd' на '4WD'
data.loc[(data['model']\
          .isin(['pajero iv', 'cayenne', 'macan'])) & (data['type_of_drive'] == "2wd"), 'type_of_drive'] = '4WD'

# заменяю оставшиеся '2wd' на 'FR'
data.loc[(data['type_of_drive'] == "2wd"), 'type_of_drive'] = 'FR'

Осталось заполнить пропуски.

In [84]:
# составляю таблицу с пропусками по моделям
type_dr_isna = (data
                   .query('type_of_drive.isna()')
                   .groupby(by=['brand', 'model'])['type_of_drive']
                   .size()
                   .reset_index()
                  )
In [85]:
# таблица без пропусков по моделям, которые есть в таблице transmission_isna
type_dr_notna = data.query('model in @type_dr_isna.model and type_of_drive.notna()')\
.groupby(by=['brand', 'model'])['type_of_drive'].size().reset_index()

# свожу таблицы transmission_notna и transmission_isna в одну, чтобы посмотреть можно ли какие-то значения заменить модой
type_dr_isna = (type_dr_isna
                   .merge(type_dr_notna, on=['brand', 'model'], how='left', suffixes=('_isna_count', '_notna_count'))
)

type_dr_isna.sort_values(by='type_of_drive_notna_count', ascending=False).head()
Out[85]:
brand model type_of_drive_isna_count type_of_drive_notna_count
66 Ravon nexia r3 361 1338.00
81 Toyota corolla 2 1087.00
22 Jac s3 317 969.00
82 Toyota hilux dc 2 825.00
23 Jac s5 105 342.00

Из имеющихся данных можно подобрать значения с использованием моды кроме: Jaguar xj, Land Rover discovery sport, Mercedes-Benz v-class, Volkswagen amarok, Renault duster. Эти автомобили не подходят, потому что у них есть комплектации с разными типами привода. Для них поставлю значение 'unknown'. Это же значение присвою для: Shacman sx3258dr384, Renault master, Renault koleos, Renault kaptur, Renault arkana, Mazda cx-9, Mazda cx-5, Mazda 6, Mazda 3, MAN tg.

Данные заполняемые вручную:

  • Полный привод: KAMAZ 4311, 43118, 43502, 44108, 53504, 65111, 65225, Scania r-series, все Уралы.

  • Передний привод: Jac iev, Renault logan, Renault dokker.

  • Задний привод - все остальные пропуски.

In [86]:
# заполняю пропуски значением 'unknown'
unk_drive = ['sx3258dr384', 'master', 'koleos', 'kaptur', 'arkana', 'cx-9', 'cx-5', '6', '3', 'tg']
change_isna(data, 'model', 'type_of_drive', unk_drive, 'unknown')

# заполняю пропуски значением 'FF'
ff_drive = ['iev', 'logan', 'dokker']
change_isna(data, 'model', 'type_of_drive', ff_drive, 'FF')

# заполняю пропуски значением '4WD'
drive_4wd = ['4311', '43118', '43502', '44108', '53504', '65111',\
             '65225', 'r-series', '32551', '4320', '4320-1951-40', '4320-1951-60', '5557']
change_isna(data, 'model', 'type_of_drive', drive_4wd, '4WD')
In [87]:
# заполняю пропуски модой
change_mode(data, 'brand', 'model', 'type_of_drive')
Количество незаполненных строк: 663
In [88]:
# оставшиеся пропуски заполняю значением 'FR'
data.type_of_drive.fillna('FR', inplace=True)

Пропуски заполнил.

Столбцы 'price_usd' и 'sale_usd'¶

In [89]:
# заменяю ',' на '.'
data[['price_usd', 'sale_usd']] = data[['price_usd', 'sale_usd']].replace(',', '.', regex=True)

# изменяю тип данных на float
data[['price_usd', 'sale_usd']] = data[['price_usd', 'sale_usd']].astype(float)

# вывожу численную характеристику столбцов
data[['price_usd', 'sale_usd']].describe()
Out[89]:
price_usd sale_usd
count 39966.00 39966.00
mean 28250.16 33757.67
std 20005.76 50702.45
min 7100.00 -35588.25
25% 15518.50 16601.93
50% 24282.27 26248.96
75% 32505.66 35000.00
max 328510.97 3778206.26

В столбце 'price_usd' явных аномалий не увидел. В столбце 'sale_usd' есть отрицательное значение - оно похоже на аномалию. Также надо проверить, есть ли значения ниже минимальной стоимости по столбцу 'price_usd'. Выведу строки со значением ниже минимального в 'price_usd'.

In [90]:
data.query('sale_usd < price_usd.min()')
Out[90]:
year month company brand model year_of_release producing_country type_of_fuel engine_volume_l transmission type_of_drive client_type quantity price_usd sale_usd area segmentation class_2013
13229 2019 8 БИПЭК АВТО Kia sportage 2019 Республика Казахстан бензин 2.00 автомат 4WD NaN NaN 20700.00 0.00 Карагандинская область Внедорожники Компактные SUV
13856 2019 8 БИПЭК АВТО Lada granta 2019 Республика Казахстан бензин 1.60 автомат FF NaN NaN 7100.00 0.00 г.Алматы Легковые автомобили B класс
15164 2019 8 БИПЭК АВТО Lada xray 2018 Республика Казахстан бензин 1.60 механика FF NaN NaN 13800.00 0.00 Павлодарская область Легковые автомобили B класс
20364 2019 8 БИПЭК АВТО Skoda superb 2019 Республика Казахстан бензин 2.00 робот 4WD NaN -1.00 35588.25 -35588.25 Жамбылская область Легковые автомобили D класс
30136 2019 8 БИПЭК АВТО UAZ 3909 2018 Республика Казахстан бензин 2.69 механика 4WD NaN NaN 11200.00 0.00 г.Алматы Коммерческие автомобили Развозные автомобили
30447 2019 8 БИПЭК АВТО UAZ patriot 2018 Республика Казахстан бензин 2.69 механика 4WD NaN NaN 13800.00 0.00 Восточно-Казахстанская область Внедорожники Среднеразмерные SUV
30908 2019 8 БИПЭК АВТО Skoda kodiaq 2019 Республика Казахстан бензин 2.00 робот 4WD NaN NaN 40000.00 0.00 г.Нур-Султан Внедорожники Полноразмерные SUV

Есть одна строка с отрицательным значением в столбцах 'sale_usd' и 'quantity'. Похоже на возврат автомобиля. Эту строку можно удалить. Также в 'sale_usd' есть значения 0. В этих строках не заполнены столбцы 'client_type', 'quantity', 'sale_usd'. Данные пропуски заполнить не могу, поэтому строки удаляю.

In [91]:
data = data.query('sale_usd > 0')
data.shape[0]
Out[91]:
39959

Удалил 7 строк.

Столбец 'quantity'¶

In [92]:
# вывожу уникальные значения в столбце
dubl(data, 'quantity')
Число уникальных значений: 55
Уникальные значения:
 [  1.   2.   4.   3.   7.   5.   8.  10.   6.   9.  11.  19.  12.  18.
  37.  17.  15.  24.  29.  14.  13.  25.  63.  23.  30.  21.  60.  22.
  16.  35.  28.  32.  54. 115.  46.  33.  47.  20.  66.  27.  50.  36.
  26.  31.  34.  40.  43.  51.  42.  38.  41. 100.  70.  79.  62.]

В этом столбце проблем уже нет, так как удалил строку с возвратом. Изменю тип столбца на целочисленный.

In [93]:
data.quantity = data.quantity.astype(int)

Столбец 'client_type'¶

In [94]:
# переведу все значения в нижний регистр
low(data, 'client_type')

# удаляю пробелы в значениях
data.client_type = data.client_type.replace(r' ', '', regex=True)

# выведу уникальные значения
dubl(data, 'client_type')
Число уникальных значений: 7
Уникальные значения:
 ['физ.лицо' 'юр.лицо' nan 'физлицо' 'юрлицо' 'корп.клиент' 'юридическое'
 'физическое']
Работа с неявными дубликатами¶
In [95]:
# устраняю неявные дубликаты
pattern_1 = ['физ\S+']
pattern_2 = ['юр\S+', 'корп\S+']

re_sub(pattern_1, data, 'client_type', 'физлицо')
re_sub(pattern_2, data, 'client_type', 'юрлицо')

client_nan = {'nan': np.NaN}
implicit_dubl(data, 'client_type', client_nan)
Количество уникальных значений после очистки: 2
Работа с пропусками¶
In [96]:
# посчитаю, сколько в столбце пропусков
data.client_type.isna().sum()
Out[96]:
7040

В столбце 7040 пропусков.

Предполагаю, что физлица обычно покупают не более 1-го автомобиля для личного пользования. Чтобы в этом убедится, посчитаю их долю от всего числа заказов, где количество автомобилей больше одного.

In [97]:
round(len(data.query('client_type == "физлицо" and quantity >= 2'))/len(data.query('quantity >= 2')), 4)
Out[97]:
0.0015

Предположение подтвердилось, что физлица очень редко покупают 2 и более автомобилей. Теперь посчитаю, как часто покупают юрлица 1 автомобиль.

In [98]:
round(len(data.query('client_type == "юрлицо" and quantity == 1'))/
      len(data.query('quantity == 1 and client_type.notna()')), 2)
Out[98]:
0.25

25 % процентов, покупающих 1 автомобиль - это юрлица, поэтому нельзя пропуски заполнить только физлицами в местах, где в заказе 1 автомобиль. Чтобы точнее определить, как заполнить пропуски создам сводную таблицу, которая будет состоять из:

  1. Сегмента.
  2. Типа клиентов.
  3. Общего количества клиентов.
  4. Доля клиентов.
In [99]:
client_nan = (data
              .query('quantity == 1')
              .groupby(['segmentation', 'client_type'])
              .size()
              .unstack()
              .reset_index().rename_axis(None, axis=1)
             )
client_nan['legal_person_count_%'] = round(client_nan['юрлицо']/(client_nan['юрлицо']+client_nan['физлицо'])*100, 2)
client_nan
Out[99]:
segmentation физлицо юрлицо legal_person_count_%
0 Внедорожники 11962 2736 18.61
1 Коммерческие автомобили 810 2653 76.61
2 Легковые автомобили 11701 2014 14.68
3 Минивэны 74 35 32.11
4 Пикапы 182 710 79.60

Исходя из полученных данных нельзя точно заполнить пропуски, потому что юрлица - заметная доля покупателей в каждом сегменте, особенно это касается коммерческих автомобилей и пикапов. Поэтому пропуски в столбце заполню значением "unknown".

In [100]:
data.loc[data.quantity == 1, 'client_type'] = data.client_type.fillna('unknown')
data.client_type.fillna('юрлицо', inplace=True)
In [101]:
len(data.query('client_type == "unknown"'))
Out[101]:
4326

В итоге не удалось заполнить 4326 пропусков в этом столбце 'client_type'.

Столбец 'area'¶

In [102]:
# вывожу уникальные значения
dubl(data, 'area')
Число уникальных значений: 18
Уникальные значения:
 ['г.Алматы' 'г.Нур-Султан' 'Восточно-Казахстанская область'
 'Западно-Казахстанская область' 'Костанайская область'
 'Павлодарская область' 'Южно-Казахстанская область'
 'Мангистауская область' 'Атырауская область' 'Актюбинская область'
 'Карагандинская область' 'Северо-Казахстанская область'
 'Акмолинская область' 'Кызылординская область' 'Жамбылская область'
 'Туркестанская область' 'Алматинская область' 'Экспорт область']

В столбце проблем не увидел.

Столбец 'segmentation'¶

In [103]:
# вывожу уникальные значения
segment = data.segmentation.unique()
segment
Out[103]:
array(['Легковые автомобили', 'Внедорожники', 'Коммерческие автомобили',
       'Минивэны', 'Пикапы'], dtype=object)

В этом столбце все в порядке.

Столбец 'class_2013'¶

In [104]:
# вывожу уникальные значения
dubl(data, 'class_2013')
Число уникальных значений: 21
Уникальные значения:
 ['C класс' 'D класс' 'E класс' 'F класс' 'Среднеразмерные SUV'
 'Полноразмерные SUV' 'Спортивные автомобили' 'Компактные SUV' 'B класс'
 'Субкомпактные SUV' 'Большие автобусы' 'Крупнотоннажные грузовики'
 'Микроавтобусы' 'Малотоннажные грузовики' 'Полноразмерный Минивэн'
 'Средние автобусы' 'Среднетоннажные грузовики' 'Pick-ups'
 'Развозные автомобили' 'A класс' 'Компактвэн']

Здесь тоже все в порядке.

Создание столбца год и месяц продажи¶

Для дальнейшего исследования данных создам столбец с месяцем и годом продажи.

In [105]:
data['date_sale'] = data.year.astype('str') + '-' + data.month.astype('str')
data['date_sale'] = pd.to_datetime(data['date_sale'], format='%Y-%m').dt.strftime("%Y-%m")

Расчет доли удаленных данных и количества оставшихся пропусков¶

In [106]:
# считаю количество строк после проведенной предобработки
all_lines_prep = len(data)
print('Количество строк после предобработки:', all_lines_prep)
print('Количество удаленных строк:', all_lines-all_lines_prep)
print('Доля удаленных данных от общего количества данных:', "{:.2%}".format(1-all_lines_prep/all_lines))
print('Kоличество пропусков для каждого столбца в процентах')
display(pd.DataFrame(round(data.isna().mean()*100, 2)).style.background_gradient('coolwarm'))
Количество строк после предобработки: 39959
Количество удаленных строк: 7
Доля удаленных данных от общего количества данных: 0.02%
Kоличество пропусков для каждого столбца в процентах
  0
year 0.000000
month 0.000000
company 0.000000
brand 0.000000
model 0.000000
year_of_release 0.000000
producing_country 0.000000
type_of_fuel 0.000000
engine_volume_l 4.910000
transmission 0.000000
type_of_drive 0.000000
client_type 0.000000
quantity 0.000000
price_usd 0.000000
sale_usd 0.000000
area 0.000000
segmentation 0.000000
class_2013 0.000000
date_sale 0.000000

Выводы¶

Датасет очищен и подготовлен для дальнейшего анализа.

В ходе предобработки данных сделал:

  • Удалил 7 срок из-за аномальных значений в столбце sale_usd, что составляет 0,02% от всех данных.
  • Заполнил часть пропусков. Пропуски остались в столбцах engine_volume_l и engine_class.
  • Убрал неявные дубликаты.
  • Проработал аномальные значения.
  • Провел категоризацию столбца 'engine_volume_l'.
  • У части столбцов изменил тип данных.
  • Из исходного датасета удалил следующие столбцы: 'Сегментация Eng', 'Форма расчета', 'Сегмент', 'Локализация производства', 'Наименование дилерского центра', 'Регион', 'Модификация'.
  • По рабочему объему двигателя автомобили разделил на следующие группы: • особо малый класс до 1,1 литра; • малый класс от 1,1 до 1,8 литра; • средний класс от 1,8 да 3,5 литра; • большой класс от 3,5 литра и выше.

Выяснил, что:

  • В датасете 32 компании, которые продают автомобили.
  • 44 бренда автомобилей.
  • 19 стран-изготовителей автомобилей.
  • Присутствуют автомобили с бензиновым, дизельным и электрическим типами двигателя.
  • 21 класс автомобилей.
  • 5 сегментов авто: Легковые автомобили, Внедорожники, Коммерческие автомобили, Минивэны, Пикапы.
  • 4 типа привода: FF, 4WD, FR, AWD.
  • 4 типа коробки передач: вариатор, механика, автомат, робот
  • Авто продают в 18 разных областях.

Исследовательский анализ¶

Распределение количества брендов на компанию¶

In [107]:
brand_in_company_kz = data.groupby('company')['brand'].nunique().sort_values(ascending=False).reset_index()
brand_in_company_kz.head()
Out[107]:
company brand
0 Allur Auto 7
1 БИПЭК АВТО 5
2 СемАЗ 4
3 Mercur Auto 3
4 Вираж 3

Из таблицы видно, что есть компании делятся на монобрендовые и мультибрендовые. Посчитаю сколько тех и других.

In [108]:
monobrand = brand_in_company_kz.query('brand == 1')['company'].nunique()
multibrand = len(brand_in_company_kz)-monobrand
print('Количество монобрендовых автоцентров:', monobrand)
print('Количество мультибрендовых автоцентров:', multibrand)
Количество монобрендовых автоцентров: 17
Количество мультибрендовых автоцентров: 15

На 2019 год в Казахстане работают 32 автодиллера. Среди них 17 продают один бренд, 15 - несколько.

In [109]:
# строю гистограмму количества брендов у автоцентров в Казахстане на 2019 год
plt.figure(figsize=(15, 8))
plt.hist(brand_in_company_kz.brand, bins=7)
plt.title('Количество брендов у автоцентров в Казахстане на 2019 год')
plt.xlabel('Количество марок')
plt.ylabel('Количество автоцентров')
plt.show();

Распределение количества моделей у брендов¶

In [110]:
model_at_brand = data.groupby('brand')['model'].nunique().sort_values(ascending=False).reset_index()
model_at_brand.head()
Out[110]:
brand model
0 KAMAZ 24
1 Mercedes-Benz 19
2 Hyundai 17
3 BMW 14
4 Volkswagen 13

Большинство компаний имеют несколько моделей. Больше всего моделей у KAMAZ - 24. Также есть производители, продающие только одну марку.

In [111]:
# расчет количества автоцентров с одной и несколькими моделями
one_model = model_at_brand.query('model == 1')['brand'].nunique()
many_model = len(model_at_brand) - one_model
print('Количество автоцентров с одной моделью:', one_model)
print('Количество автоцентров с несколькими моделями:', many_model)
Количество автоцентров с одной моделью: 5
Количество автоцентров с несколькими моделями: 39

Всего 44 марки автомобилей продают в Казахстане. Из них 5 продают только одну модель, 39 - несколько.

In [112]:
# строю гистограмму количества моделей у брендов в Казахстане в 2019 году
plt.figure(figsize=(15, 8))
plt.hist(model_at_brand.model, bins=24)
plt.title('Количество моделей у брендов в Казахстане в 2019 году')
plt.xlabel('Количество моделей')
plt.ylabel('Количество брендов')
plt.show();

Распределение количества автомобилей по году выпуска¶

In [113]:
release_of_cars = data.groupby('year_of_release')['quantity'].sum().sort_values(ascending=False).reset_index()

# расчет доли автомобилей по годам
release_of_cars['quantity_ratio'] = round((release_of_cars.quantity/data.quantity.sum()), 3)
release_of_cars
Out[113]:
year_of_release quantity quantity_ratio
0 2019 42027 0.78
1 2018 11419 0.21
2 2017 394 0.01
3 2016 30 0.00
4 2014 2 0.00
5 0 1 0.00
6 2011 1 0.00
7 2013 1 0.00

В 2019 году продали 42027 машин, выпущенных в 2019 году, что составляет 78 %. 11419 или 21 % - автомобили 2018 года.

Распределение количества проданных автомобилей по странам¶

In [114]:
manufacturing_countries = data.groupby('producing_country')['quantity'].sum().sort_values(ascending=False).reset_index()
manufacturing_countries['quantity_ratio'] = round((manufacturing_countries.quantity/data.quantity.sum()), 3)
manufacturing_countries.head()
Out[114]:
producing_country quantity quantity_ratio
0 Республика Казахстан 31366 0.58
1 Российская Федерация 14341 0.27
2 Япония 4612 0.09
3 Турция 1167 0.02
4 Таиланд 981 0.02

Больше всего было продано автомобилей произведенных в Казахстане - 58 %. На втором месте Российская Федерация - 27 %. На третьем Япония - 8,6 % количество автомобилей было продано.

Распределение количества проданных машин по типу двигателя¶

In [115]:
sale_by_engine_type = data.groupby('type_of_fuel')['quantity'].sum().sort_values(ascending=False).reset_index()
sale_by_engine_type['quantity_ratio'] = round(sale_by_engine_type.quantity/data.quantity.sum(), 2)
sale_by_engine_type
Out[115]:
type_of_fuel quantity quantity_ratio
0 бензин 51008 0.95
1 дизель 2809 0.05
2 unknown 34 0.00
3 электричество 14 0.00
4 гибрид 10 0.00

95 % рынка приходится на автомобили с бензиновыми двигателями. 5 % - на дизельные двигатели.

Распределение автомобилей по объему двигателя¶

Построю диаграмму размаха без учета пропусков.

In [116]:
plt.figure(figsize=(15, 8))
sns.boxplot(data.query('engine_volume_l > 0').engine_volume_l)
plt.title('Распределение автомобилей по объему двигателя')
plt.xlabel('Объем двигателя, л')
plt.show()
In [117]:
print('Минимальный объем двигателя:', data.query('engine_volume_l > 0').engine_volume_l.min())
print('Среднее значение объема двигателя:', round(data.query('engine_volume_l > 0').engine_volume_l.mean(), 2))
print('Максимальный объем двигателя:', data.query('engine_volume_l > 0').engine_volume_l.max())
Минимальный объем двигателя: 1.2
Среднее значение объема двигателя: 2.32
Максимальный объем двигателя: 13.0

На диаграмме видны выбросы. Посмотрю, как будет выглядеть распределение, если удалить выбросы. Удалять буду с помощью межквартильного диапазона.

In [118]:
# посчитаю значения квантилей
q1 = np.quantile(data.query('engine_volume_l > 0').engine_volume_l, .25)
q3 = np.quantile(data.query('engine_volume_l > 0').engine_volume_l, .75)
iqr = q3 - q1

# рассчитываю базовую нижнюю и верхнюю границу диапазона для исключения выбросов
upper_eng = q3 + 1.5 * iqr
lower_eng = q1 - 1.5 * iqr

# создам переменную, в которой будут отброшенные выбросы
not_blowout_eng = data.query('(engine_volume_l > @lower_eng) and (engine_volume_l < @upper_eng) and engine_volume_l > 0')

print('Потери при удалении выбросов:', round((1- len(not_blowout_eng)/data.shape[0]), 4))
Потери при удалении выбросов: 0.0985
In [119]:
# строю даиграмму размаха с отсечением выбросов
plt.figure(figsize=(15, 8))
sns.boxplot(not_blowout_eng.engine_volume_l)
plt.title('Распределение автомобилей по объему двигателя')
plt.xlabel('Объем двигателя, л')
plt.show()

Если удалить выбросы, то потери составят 10 % от всего датасета. Это много, поэтому не удаляю.

Распределение типов трансмиссии по проданным автомобилям¶

In [120]:
# функция для расчета распределений относительно количества автомобилей в заказе
def distrib(df, column_distr, column_regar):
    df_distr = df.groupby(column_distr)[column_regar].sum().sort_values(ascending=False).reset_index()
    df_distr['quantity_ratio'] = round(df_distr[column_regar]/df[column_regar].sum(), 2)
    display(df_distr.head())
In [121]:
distrib(data, 'transmission', 'quantity')
transmission quantity quantity_ratio
0 автомат 28397 0.53
1 механика 17834 0.33
2 вариатор 4313 0.08
3 unknown 2841 0.05
4 робот 490 0.01

В Казахстане в 2019 году наиболее популярны автомобили с автоматом - 53 %. На втором месте - с механикой - 33%

Распределение проданных автомобилей по типу привода¶

In [122]:
distrib(data, 'type_of_drive', 'quantity')
type_of_drive quantity quantity_ratio
0 FF 29347 0.54
1 4WD 18998 0.35
2 FR 3730 0.07
3 AWD 1057 0.02
4 unknown 743 0.01

Чаще всего покупали в 2019 году технику с передним приводом - 54 %. На втором месте с полным - 35 %.

Распределение количества проданных автомобилей по типу клиента¶

In [123]:
distrib(data, 'client_type', 'quantity')
client_type quantity quantity_ratio
0 юрлицо 24807 0.46
1 физлицо 24742 0.46
2 unknown 4326 0.08

Физические и юридические лица покупают примерно одинаковое количество автомобилей 46 %. 8 % приходится на строки, где достоверно нельзя указать тип покупателя.

Распределение количества автомобилей в заказе¶

In [124]:
# строю гистограмму количества моделей у брендов в Казахстане в 2019 году
plt.figure(figsize=(15, 8))
plt.hist(data.quantity)
plt.title('Распределение количества автомобилей в одном заказе')
plt.xlabel('Количество')
plt.ylabel('Количество автомобилей')
plt.show();

В основном заказ содержит один автомобиль, намного реже 2-3. Наибольшее число техники в заказе - 115 шт. Интересно посмотреть, как распределяются заказы с большим числом автомобилей по брендам, начиная от 15 штук.

In [125]:
print('Количество продаж:', data.query('quantity >= 15').shape[0])
print(data.query('quantity >= 15')['brand'].unique())
Количество продаж: 248
['Foton' 'Hyundai' 'Kia' 'Lada' 'MAN' 'Skoda' 'UAZ' 'Урал' 'Daewoo'
 'Shacman' 'Dongfeng' 'Jac' 'Volkswagen']

Всего таких продаж 248. Их делали 13 различных бренда.

Распределение цен¶

In [126]:
# диаграмма размаха для распределения цен
plt.figure(figsize=(15, 8))
sns.boxplot(data.price_usd)
plt.title('Распределение цен')
plt.xlabel('Цена, USD')
plt.show()
In [127]:
print('Минимальная цена:', data.query('engine_volume_l > 0').price_usd.min())
print('Средняя цена:', round(data.query('engine_volume_l > 0').price_usd.mean(), 2))
print('Максимальный цена:', data.query('engine_volume_l > 0').price_usd.max())
Минимальная цена: 7100.0
Средняя цена: 27982.4
Максимальный цена: 328510.9706

В основном цена на автомобили на рынке варьируется от 10 до 55 тысяч долларов. На диаграмме видны выбросы. Посчитаю, сколько они занимают от всего датасета.

In [128]:
# посчитаю значения квантилей
q1_price = np.quantile(data.price_usd, .25)
q3_price = np.quantile(data.price_usd, .75)
iqr_price = q3_price - q1_price

# рассчитываю базовую нижнюю и верхнюю границу диапазона для исключения выбросов
upper_price = q3_price + 1.5 * iqr_price
lower_price = q1_price - 1.5 * iqr_price

# создам переменную, в которой будут отброшенные выбросы
price_clean = data.query('price_usd > @lower_price and price_usd < @upper_price')

print('Потери при удалении выбросов:', round(1-(len(price_clean)/len(data)), 3))
Потери при удалении выбросов: 0.071

Если удалить выбросы, то потеряю 7 % данных, а эта потеря может исказить результаты исследования. Оставляю.

Распределение продаж по классу автомобилей¶

In [129]:
distrib(data, 'class_2013', 'quantity')
class_2013 quantity quantity_ratio
0 B класс 13846 0.26
1 Субкомпактные SUV 7681 0.14
2 Компактные SUV 7346 0.14
3 D класс 5566 0.10
4 Среднеразмерные SUV 4401 0.08

26 % рынка занимает В класс, по 14 % у Субкомпактных и Компактных SUV.

Категоризация данных¶

В столбце 'engine_volume_l' и 'price_usd' много разных уникальных значений, что неудобно для анализа, поэтому для дальнейшего анализа их лучше всего категоризировать.

Столбец 'engine_volume_l'¶

По рабочему объему двигателя автомобили делят на следующие группы:

  • особо малый класс до 1,1 литра;
  • малый класс от 1,1 до 1,8 литра;
  • средний класс от 1,8 да 3,5 литра;
  • большой класс от 3,5 литра и выше.

На основании этих данных проведу категоризацию автомобилей по объему двигателя (столбец engine_volume_l).

In [130]:
volume_labels = ['особо малый', 'малый', 'средний', 'большой']
volume_bins = [0, 1.2, 1.8, 3.5, max(data.engine_volume_l)]
In [131]:
data['engine_class'] = pd.cut(data[data.engine_volume_l !=0].engine_volume_l, bins=volume_bins, labels=volume_labels)
data.head()
Out[131]:
year month company brand model year_of_release producing_country type_of_fuel engine_volume_l transmission type_of_drive client_type quantity price_usd sale_usd area segmentation class_2013 date_sale engine_class
0 2019 5 Mercur Auto Audi a3 2018 Германия бензин 1.40 робот FF физлицо 1 28115.00 28115.00 г.Алматы Легковые автомобили C класс 2019-05 малый
1 2019 8 Mercur Auto Audi a3 2018 Германия бензин 1.40 робот FF юрлицо 1 32246.99 32246.99 г.Алматы Легковые автомобили C класс 2019-08 малый
2 2019 4 Mercur Auto Audi a4 2018 Германия бензин 1.40 робот FF физлицо 1 32000.00 32000.00 г.Алматы Легковые автомобили D класс 2019-04 малый
3 2019 7 Mercur Auto Audi a4 2018 Германия бензин 1.40 робот FF юрлицо 1 31929.00 31929.00 г.Алматы Легковые автомобили D класс 2019-07 малый
4 2019 7 Mercur Auto Audi a4 2018 Германия бензин 1.40 робот FF физлицо 1 31929.00 31929.00 г.Алматы Легковые автомобили D класс 2019-07 малый

Столбец 'price_usd'¶

Категоризацию столбца проведу на основании очищенного от выбросов датасета. Он хранится в переменной not_blowout_eng. Значения столбца разделю на четыре равные части по квартилям.

In [132]:
volume_bins = [[0], stat.quantiles(not_blowout_eng.price_usd, n=4, method='exclusive'), [max(data.price_usd)]]

# список списков перевожу в обычный список
volume_bins = list(itertools.chain(*volume_bins))
volume_bins
Out[132]:
[0, 15261.97794, 22709.4814, 30993.33102, 328510.9706]

На основании полученных данных создам следующие сегменты по цене:

  • низкая - до 15261.97794
  • средняя - от 15261.97794 до 22709.4814
  • выше среднего - от 22709.4814 до 30993.33102
  • высокая - от 30993.33102
In [133]:
volume_labels = ['низкая', 'средняя', 'выше среднего', 'высокая']
In [134]:
data['price_cat'] = pd.cut(data.price_usd, bins=volume_bins, labels=volume_labels)
data.head()
Out[134]:
year month company brand model year_of_release producing_country type_of_fuel engine_volume_l transmission type_of_drive client_type quantity price_usd sale_usd area segmentation class_2013 date_sale engine_class price_cat
0 2019 5 Mercur Auto Audi a3 2018 Германия бензин 1.40 робот FF физлицо 1 28115.00 28115.00 г.Алматы Легковые автомобили C класс 2019-05 малый выше среднего
1 2019 8 Mercur Auto Audi a3 2018 Германия бензин 1.40 робот FF юрлицо 1 32246.99 32246.99 г.Алматы Легковые автомобили C класс 2019-08 малый высокая
2 2019 4 Mercur Auto Audi a4 2018 Германия бензин 1.40 робот FF физлицо 1 32000.00 32000.00 г.Алматы Легковые автомобили D класс 2019-04 малый высокая
3 2019 7 Mercur Auto Audi a4 2018 Германия бензин 1.40 робот FF юрлицо 1 31929.00 31929.00 г.Алматы Легковые автомобили D класс 2019-07 малый высокая
4 2019 7 Mercur Auto Audi a4 2018 Германия бензин 1.40 робот FF физлицо 1 31929.00 31929.00 г.Алматы Легковые автомобили D класс 2019-07 малый высокая

Корреляционный анализ¶

Наличие зависимостей между признаками в датасете выбираю следующие: 'year_of_release', 'type_of_fuel', 'engine_class', 'sale_usd', 'transmission', 'client_type', 'type_of_drive', 'segmentation'.

Для проведения корреляционного анализа подготовлю данные:

  • Удалю строки с пропусками.
  • Закодирую категориальные переменные.
In [135]:
# удаляю пропуски
corr = data.query('year_of_release != 0 and engine_volume_l != 0 and type_of_fuel != "unknown" and \
transmission != "unknown" and type_of_drive != "unknown" and engine_class != "0" and client_type != "unknown"')
In [136]:
corr = data[['year_of_release', 'type_of_fuel', 'engine_class', \
        'transmission', 'client_type', 'type_of_drive', 'price_cat', 'segmentation']]
In [137]:
# кодирую категориальные признаки числами
corr.type_of_fuel = corr.type_of_fuel.map({'бензин': 1, 'дизель': 2, 'гибрид': 3, 'электричество': 4})
corr.engine_class = corr.engine_class.map({'особо малый': 1, 'малый': 2, 'средний': 3, 'большой': 4})
corr.transmission = corr.transmission.map({'робот': 1, 'автомат': 2, 'механика': 3, 'вариатор': 4})
corr.type_of_drive = corr.type_of_drive.map({'FF': 1, '4WD': 2, 'FR': 3, 'AWD': 4})
corr.client_type = corr.client_type.map({'физлицо': 1, 'юрлицо': 2})
segment_change_dict = {n: i for n, i in zip(corr.segmentation.unique().tolist(), list(range(1, 6)))}
corr.segmentation = corr.segmentation.map(segment_change_dict)

Поскольку в данных присутствуют категориальные переменные для поиска зависимости буду использовать коэффициент корреляции Phi_K.

In [138]:
# строю матрицу корреляции
plt.figure(figsize=(10, 6))
plt.title('Корреляция признаков по коэффициенту Phi_K')
sns.heatmap(corr.phik_matrix(verbose=False), linewidths=2, square=True, cbar=False, cmap='Greens', annot=True);

Для оценки зависимостей использовал шкалу Чеддока. На диаграмме наблюдается:

  1. Умеренная взаимосвязь:
    • между типом двигателя по топливу с объемом двигателя, трансмиссией и сегментом;
    • объема двигателя с сегментом;
    • трансмиссии с типом двигателя по топливу и с сегментом;
    • типа клиента с типом привода и с сегментом.
  2. Заметная взаимосвязь:
    • между типом двигателя по топливу и типом привода;
    • типа трансмиссии с типом привода и с типом клиента;
    • между типом привода и сегментом автомобиля;
    • цены с типом двигателя по топливу и с его объемом.
  3. Высокая взаимосвязь наблюдается:
    • между объемом двигателя и типом привода;
    • между ценовой категорией и объемом двигателя.

Выводы¶

  • На 2019 год в Казахстане работают 32 автодилера. Среди них 15 продают один бренд, 17 - несколько.
  • Всего 44 марки автомобилей продают в Казахстане. Из них 5 продают только одну модель, 39 - несколько.
  • В 2019 году продали 42027 машин, выпущенных в 2019 году, что составляет 78 %. 11419 или 21 % - автомобили 2018 года
  • Больше всего было продано автомобилей произведенных в Казахстане - 58 %. На втором месте Российская Федерация - 27 %. На третьем Япония - 8,6 % количество автомобилей было продано.
  • 95 % рынка приходится на автомобили с бензиновыми двигателями. 5 % - на дизельные двигатели.
  • В Казахстане в 2019 году наиболее популярны автомобили с автоматом - 53 %. На втором месте - с механикой - 32%
  • Чаще всего покупали в 2019 году технику с передним приводом - 54 %. На втором месте с полным - 34 %.
  • Физические лица чаще покупают автомобили - 54 %. Юридические лица составляют 46 % от всех покупателей, эта цифра обусловлена тем, что у них в заказах чаще всего больше одного автомобиля.
  • В основном заказ содержит один автомобиль, намного реже 2-3. Наибольшее число техники в заказе - 115 шт.
  • В основном цена на автомобили на рынке варьируется от 10 до 55 тысяч долларов.
  • 26 % рынка занимает В класс, по 14 % у Субкомпактных и Компактных SUV.
  • Корреляционный анализ показал, что есть:
  1. Умеренная взаимосвязь:
    • между типом двигателя по топливу с объемом двигателя, трансмиссией и сегментом;
    • объема двигателя с сегментом;
    • трансмиссии с типом двигателя по топливу и с сегментом;
    • типа клиента с типом привода и с сегментом.
  2. Заметная взаимосвязь:
    • между типом двигателя по топливу и типом привода;
    • типа трансмиссии с типом привода и с типом клиента;
    • между типом привода и сегментом автомобиля;
    • цены с типом двигателя по топливу и с его объемом.
  3. Высокая взаимосвязь наблюдается:
    • между объемом двигателя и типом привода;
    • между ценовой категорией и объемом двигателя.

Анализ рынка продаж автомобилей в Казахстане в 2019 году¶

Емкость рынка¶

Рынок автомобилей делится на коммерческие и некоммерческие. Поэтому в этом разрезе и буду смотреть на продажи. Но для начала посчитаю полную емкость рынка.

Емкость для всего рынка¶

Емкость рынка в количественном выражении - общий объем реализованной продукции за период. Поэтому чтобы рассчитать емкость рынка в количественном выражении, надо сложить все значения в столбце 'quantity' (количество автомобилей в заказе).

In [139]:
size_quantity = data.quantity.sum()
print('Емкость рынка в количественном выражении с января по сентябрь в 2019 году в Казахстане составила', \
      size_quantity, 'автомобилей.')
Емкость рынка в количественном выражении с января по сентябрь в 2019 году в Казахстане составила 53875 автомобилей.

Далее посчитаю какова предполагаемая емкость за весь 2019 год. За предыдущие 10 лет емкость продаж в четвертом квартале составляла 30-50 % от общегодового. Поэтому для прогноза возьму усредненный показатель в 40 %.

In [140]:
forecast_size_quantity = round(data.quantity.sum() + (data.quantity.sum() * 0.4))
print('Прогнозируемая количественная емкость составляет', \
      forecast_size_quantity, 'автомобилей.')
Прогнозируемая количественная емкость составляет 75425 автомобилей.

Теперь посчитаю емкость продаж в денежном выражении за 9 месяцев и прогнозируемую за весь год. Для этого сложу все цены заказа в столбце 'sale_usd'.

In [141]:
size_sale = data.sale_usd.sum()
print(f'Емкость в денежном выражении за 9 месяцев составляет \
{size_sale:,.2f} доллара'.replace(',', ' '))

forecast_size_sale = (data.sale_usd.sum() + data.sale_usd.sum() * 0.4)
print(f'Прогнозируемая емкость в денежном выражении за 2019 год составляет \
{forecast_size_sale:,.2f} доллара'.replace(',', ' '))
Емкость в денежном выражении за 9 месяцев составляет 1 349 194 556.31 доллара
Прогнозируемая емкость в денежном выражении за 2019 год составляет 1 888 872 378.83 доллара

Емкость коммерческого транспорта на рынке¶

Рассчитаю емкость в денежном и количественном выражениях за 9 месяцев 2019 года.

In [142]:
commercial_quantity = round(data.query('segmentation == "Коммерческие автомобили"').quantity.sum())
print('Емкость рынка в количественном выражении составила', commercial_quantity, 'коммерческих автомобилей.')

commercial_sale = data[data.segmentation == "Коммерческие автомобили"].sale_usd.sum()
print(f'Емкость рынка в денежном выражении составила \
{commercial_sale:,.2f} доллара коммерческих автомобилей.'.replace(',',' '))
Емкость рынка в количественном выражении составила 5246 коммерческих автомобилей.
Емкость рынка в денежном выражении составила 185 811 934.25 доллара коммерческих автомобилей.

Рассчитаю предполагаемую емкость в денежном и количественном выражениях на 2019 год.

In [143]:
comm_quant_forecast = round(commercial_quantity + commercial_quantity * 0.4)
print('Прогнозируемая емкость рынка в количественном выражении составит', \
comm_quant_forecast, 'коммерческих автомобиля.')

comm_sale_forecast = commercial_sale + commercial_sale * 0.4
print(f'Прогнозируемая емкость рынка в денежном выражении составит \
{comm_sale_forecast:,.2f} доллара для коммерческих автомобилей.'.replace(',',' '))
Прогнозируемая емкость рынка в количественном выражении составит 7344 коммерческих автомобиля.
Прогнозируемая емкость рынка в денежном выражении составит 260 136 707.96 доллара для коммерческих автомобилей.

Емкость некоммерческого транспорта на рынке¶

Рассчитаю емкость рынка в денежном и количественном выражениях за 9 месяцев 2019 года.

In [144]:
nocommercial_quantity = size_quantity - commercial_quantity
print('Емкость рынка в количественном выражении составила', nocommercial_quantity, 'некоммерческих автомобилей.')

nocommercial_sale = size_sale - commercial_sale
print(f'Емкость рынка в денежном выражении составила \
{nocommercial_sale:,.2f} доллара для некоммерческих автомобилей.'.replace(',',' '))
Емкость рынка в количественном выражении составила 48629 некоммерческих автомобилей.
Емкость рынка в денежном выражении составила 1 163 382 622.05 доллара для некоммерческих автомобилей.

Рассчитаю прогнозируемую емкость на 2019 год.

In [145]:
nocomm_quant_forecast = forecast_size_quantity - comm_quant_forecast
print('Прогнозируемая емкость рынка в количественном выражении составит', \
nocomm_quant_forecast, 'некоммерческих автомобилей.')

nocomm_sale_forecast = forecast_size_sale - comm_sale_forecast
print(f'Прогнозируемая емкость рынка в денежном выражении составит \
{nocomm_sale_forecast:,.2f} доллара для некоммерческих автомобилей.'.replace(',',' '))
Прогнозируемая емкость рынка в количественном выражении составит 68081 некоммерческих автомобилей.
Прогнозируемая емкость рынка в денежном выражении составит 1 628 735 670.88 доллара для некоммерческих автомобилей.

Диаграммы долей коммерческого и некоммерческого транспорта на рынке¶

In [146]:
# размещение диаграмм рядом
fig = make_subplots(rows=1, cols=2, specs=[[{"type": "pie"}, {"type": "pie"}]],
                    subplot_titles=['Количественная емкость рынка', 'Емкость рынка в USD'])

# диаграмма для количественной емкости рынка
fig.add_trace(go.Pie(labels=['коммерческий', 'некоммерческий'],
                     values= [commercial_quantity, nocommercial_quantity],
                     texttemplate = "%{percent: .1%}",
                     hole=.4,
                     domain=dict(x=[0.5, 0.5]),
                     name=""),
              row=1, col=1)

# диаграмма для емкости рынка в денежном выражении
fig.add_trace(go.Pie(labels=['коммерческий', 'некоммерческий'],
                     values= [commercial_sale, nocommercial_sale],
                     texttemplate = "%{percent: .1%}",
                     hole=.4,
                     domain=dict(x=[1, 1]),
                     name=""),
              row=1, col=2)

# добавляю название
fig.update_layout(
    title="Доли коммерческого и некоммерческого транспорта на рынке в Казахстане в 2019 году",
    title_x = 0.45)

fig.show()

Коммерческий транспорт в 2019 году в Казахстане занимал 9,7 % в количественном выражении и 13,8 % в денежном. Некоммерческий транспорт 90,3 % и 86,2 % соответственно.

Выводы¶

За 9 первых месяцев 2019 года в Казахстане было продано 53875 автомобиля из них 5246 или 9,7 % приходится на коммерческий транспорт, 90,3 % или 48629 автомобилей на некоммерческий транспорт. Выручка со всех продаж составила 1 миллиард 349 миллионов долларов. 185 миллионов 811 тысяч или 13,8 % приходится на коммерческий транспорт, 86,2 % или 1 миллиард 163 миллиона - на некоммерческий.

Определение марок - лидеров на рынке автомобилей¶

Марки-лидеры по всему рынку¶

Чтобы определить марки-лидеры, сгруппирую датасет по ним и просуммирую количество техники в заказе и стоимость заказа.

In [147]:
# топ лидеров в общем на рынке по продажам в денежном эквиваленте
brand_top_sale = (data
                  .groupby('brand')['sale_usd']
                  .sum()
                  .sort_values(ascending=False)
                  .reset_index()
                 )

# топ лидеров в общем на рынке по продажам по количеству техники
brand_top_quantity = (data
                      .groupby('brand')['quantity']
                      .sum()
                      .sort_values(ascending=False)
                      .reset_index()
                     )
In [148]:
# код для отображения двух фреймов рядом
df1_styler = (brand_top_sale
              .head(10)
              .style
              .set_table_attributes("style='display:inline'")
              .set_caption('Топ-10 брендов автомобилей по продажам в USD')
             )

df2_styler = (brand_top_quantity
              .head(10)
              .style
              .set_table_attributes("style='display:inline'")
              .set_caption('Топ-10 брендов по количеству проданных автомобилей')
             )

space = "\xa0" * 20  # создаю промежуток между фреймами
display_html(df1_styler._repr_html_()+ space + df2_styler._repr_html_(), raw=True)
Топ-10 брендов автомобилей по продажам в USD
  brand sale_usd
0 Toyota 402500067.912330
1 Hyundai 239018204.781610
2 Lada 107847478.947374
3 Lexus 77376371.520560
4 KAMAZ 46092683.388360
5 Kia 45669700.000000
6 Nissan 40012772.489470
7 GAZ 32286199.162010
8 Jac 30589381.108639
9 ANKAI 28907055.438830
                    
Топ-10 брендов по количеству проданных автомобилей
  brand quantity
0 Lada 11092
1 Toyota 10745
2 Hyundai 10404
3 Kia 2358
4 Jac 2099
5 GAZ 1821
6 Renault 1796
7 Ravon 1795
8 Nissan 1664
9 UAZ 1361
In [149]:
# сртою столбчатую диаграмму
fig = px.bar(brand_top_sale.head(10),
             x='sale_usd',
             y='brand',
             color = 'brand',
             hover_name="brand"
            )

# добавляю название и подписи осей
fig.update_layout(
    title="Топ-10 брендов автомобилей по продажам в USD",
    xaxis_title='Продажи, USD',
    yaxis_title='Бренд',
    title_x = 0.5
)

fig.show()
In [150]:
# сртою столбчатую диаграмму
fig = px.bar(brand_top_quantity.head(10),
             x='quantity',
             y='brand',
             color = 'brand',
             hover_name="brand"
            )

# добавляю название и подписи осей
fig.update_layout(
    title="Топ-10 брендов по количеству проданных автомобилей",
    xaxis_title='Продажи, шт.',
    yaxis_title='Бренд',
    title_x = 0.5
)

fig.show()

По количеству вырученных денег с большим отрывом лидирует Toyota, на втором месте Hyundai, на третьем Lada. По количеству проданных автомобилей рейтинг выглядит иначе. Lada на первом месте, 2-е у Toyota, 3-е Hyundai. Разрыв между топ-3 не сильный.

Расположение брендов с 4-го по 10-е место сильно отличаются:

  • В топ-10 по выручке входят: Lexus, KAMAZ, Kia, Nissan, GAZ, Jac, ANKAI.
  • В топ-10 по количеству штук: Kia, Jac, GAZ, Renault, Ravon, Nissan, UAZ.

Расчет марок-лодеров коммерческого и некоммерческого транспорта¶

In [151]:
# топ лидеров в общем на рынке по продажам в денежном эквиваленте
brand_top_sale_commerce = (data
                           .query('segmentation == "Коммерческие автомобили"')
                           .groupby('brand')['sale_usd', 'quantity']
                           .sum()
                           .sort_values('sale_usd', ascending=False)
                           .reset_index()
                          )

# топ лидеров в общем на рынке по продажам по количеству техники
brand_top_quant_nocommerce = (data
                              .query('segmentation != "Коммерческие автомобили"')
                              .groupby('brand')['sale_usd', 'quantity']
                              .sum()
                              .sort_values('sale_usd', ascending=False)
                              .reset_index()
                             )
In [152]:
# код для отображения двух фреймов рядом
df1_styler_comm = (brand_top_sale_commerce
                   .head(10)
                   .style
                   .set_table_attributes("style='display:inline'")
                   .set_caption('Топ-10 брендов по продажам коммерческих автомобилей')
                  )

df2_styler_noncom = (brand_top_quant_nocommerce
                     .head(10)
                     .style
                     .set_table_attributes("style='display:inline'")
                     .set_caption('Топ-10 брендов по продажам некоммерческих автомобилей')
                    )

space = "\xa0" * 20  # создаю промежуток между фреймами
display_html(df1_styler_comm._repr_html_()+ space + df2_styler_noncom._repr_html_(), raw=True)
Топ-10 брендов по продажам коммерческих автомобилей
  brand sale_usd quantity
0 KAMAZ 46092683.388360 892
1 GAZ 32286199.162010 1821
2 ANKAI 28907055.438830 300
3 Daewoo 12162701.935390 146
4 UAZ 11248278.170553 844
5 Hyundai 9710608.677650 236
6 MAN 7799873.323200 69
7 Shacman 5468192.145900 101
8 Dongfeng 4374488.400940 160
9 Mercedes-Benz 4271360.708800 56
                    
Топ-10 брендов по продажам некоммерческих автомобилей
  brand sale_usd quantity
0 Toyota 402500067.912330 10745
1 Hyundai 229307596.103960 10168
2 Lada 107847478.947374 11092
3 Lexus 77376371.520560 1206
4 Kia 45669700.000000 2358
5 Nissan 40012772.489470 1664
6 BMW 28735768.370860 438
7 Jac 26595338.191695 1907
8 Volkswagen 25149673.093390 1287
9 Renault 24184906.234080 1796
In [153]:
# размещение диаграмм рядом
fig = make_subplots(rows=1, cols=2, specs=[[{"type": "pie"}, {"type": "pie"}]],
                    subplot_titles=['Коммерческий транспорт', 'Некоммерческий транспорт'])

# диаграмма для количественной емкости рынка
fig.add_trace(go.Pie(labels=brand_top_sale_commerce['brand'].head(10),
                     values= brand_top_sale_commerce['sale_usd'],
                     texttemplate = "%{label} <br>%{percent: .1%}",
                     hole=.4,
                     domain=dict(x=[0.5, 0.5]),
                     ),
              row=1, col=1)

# диаграмма для емкости рынка в денежном выражении
fig.add_trace(go.Pie(labels=brand_top_quant_nocommerce['brand'].head(10),
                     values= brand_top_quant_nocommerce['sale_usd'],
                     texttemplate = "%{label} <br>%{percent: .1%}",
                     hole=.4,
                     domain=dict(x=[1, 1])
                     ),
              row=1, col=2)

# добавляю название
fig.update_layout(
    title="Доли марок на рынке в Казахстане в 2019 году",
    title_x = 0.45,
    showlegend=False

)

fig.show()

Коммерческий сегмент

Среди коммерческого транспорта первое место на рынке 28,4 % занимает Камаз, на втором GAZ - 19,9 %. В Казахстане юрлица предпочитают покупать автомобили из России. Третье место с 17,8 % у китайского бренда ANKAI. Эти три марки занимают почти 66 % рынка. 55 % рынка занимают Российские производители транспорта.

Некоммерческий сегмент

В нем лидируют японский бренд Toyota, который занимает 40 % потребительского рынка, далее идет Hyundai с 22,8 %. Третье место Lada - 10,7 %. Эти три марки захватили 73 % всего рынка. Японские производители занимают 56 % рынка.

Динамика рынка автомобилей в Казахстане за 2019 год¶

Общая динамика рынка¶

In [154]:
# создаю таблицу с изменением цены по месяцам
dynamics_graph = data.groupby('date_sale').sale_usd.sum().reset_index()
In [155]:
# строю график общей динамики рынка
plt.figure(figsize=(15, 8))
plt.plot(dynamics_graph.date_sale,
         dynamics_graph.sale_usd,
         linewidth=2,
         marker='s')

# добавление значений на график
for x, y, tex in zip(dynamics_graph.date_sale, dynamics_graph.sale_usd, dynamics_graph.sale_usd):
    t = plt.text(x, y, int(tex/1000),
                 horizontalalignment='right',
                 rotation='horizontal',
                 verticalalignment='bottom',
                 fontdict={'color':'black', 'weight':'bold'})

plt.title('Общая динамика продаж автомобилей в Казахстане в 2019 году (тыс.USD)')
plt.xlabel('Месяц продажи')
plt.ylabel('Объем продаж, USD')

# убираю ось Y
ax = plt.gca()
ax.get_yaxis().set_visible(False)

plt.show()

На графике видно, что начиная с марта до мая объем продаж автомобилей рос. Далее в июне он вышел на плато, а затем резкий рост в июле, за которым последовал такой же резкий спад.

Чтобы получить дополнительную информацию, разобью график объема продаж на коммерческий и некоммерческий транспорт.

Динамика объемов продаж коммерческих и некоммерческих автомобилей¶

In [156]:
dynamics_graph_commers = (data
                          .query('segmentation == "Коммерческие автомобили"')
                          .groupby('date_sale').sale_usd.sum().reset_index()
                         )
dynamics_graph_nocommers = (data
                          .query('segmentation != "Коммерческие автомобили"')
                          .groupby('date_sale').sale_usd.sum().reset_index()
                         )
In [157]:
# строю график общей динамики рынка
plt.figure(figsize=(15, 8))
plt.plot(dynamics_graph_commers.date_sale,
         dynamics_graph_commers.sale_usd,
         linewidth=2,
         marker='s',
        label='Динамика продаж коммерческого транспорта'
        )

plt.plot(dynamics_graph_nocommers.date_sale,
         dynamics_graph_nocommers.sale_usd,
         linewidth=2,
         marker='s',
         label='Динамика продаж некоммерческого транспорта'
        )

plt.title('Динамика продаж автомобилей в Казахстане в 2019 году в USD')
plt.xlabel('Месяц продажи')
plt.ylabel('Объем продаж, USD')
plt.legend(loc="upper left")

plt.show()

Выводы

  • В начале года роста не было в обоих сегментах Так как начало года, да и к тому же в феврале происходили протесты.
  • С марта до июля в некоммерческом сегменте заметен рост продаж.
  • А вот у коммерческого сегмента рост сменяет падение и наоборот. Предполагаю что падения продаж в коммерческом сегменте были вызваны нестабильной политической ситуацией в стране: Нурсултан Назарбаев в середине марта досрочно ушел с поста президента, а в июне были выборы нового президента, после чего проходили митинги.
  • В июле в коммерческом сегменте резкий рост, скорее всего, причина в том, что политическая ситуация начала стабилизироваться.
  • Из сторонних источников выяснил, что резкий спад в обоих сегментах в августе произошел не из-за падения спроса, а из-за дефицита автомобилей у дилеров. Возможно, он был вызван дефицитом полупроводников на заводах-изготовителях техники. Потому что во всем мире поставки полупроводников начали сокращаться во втором квартале 2018 года. Так происходило до первого квартала 2019. Также у дилеров возникли проблемы с доставкой автомобилей через Китай. Посмотрю, что скажет анализ дальше.

Динамика продаж автомобилей по сегментам¶

In [158]:
# функция для графиков динамики продаж
def dynamics(df, index, columns, values, aggfunc, typ):
    df = (df
          .pivot_table(index=index,
                       columns=columns,
                       values=values,
                       aggfunc=aggfunc)
         )
    # устанавливаю размеры графиков
    color = ['green', 'b', 'r', 'black', 'y', 'm', 'c']
    df.plot(figsize=(15, 8), marker='s', color=color)

    plt.xlabel('Месяц продажи')
    plt.ylabel('Объем продаж, USD')
    plt.title('Динамика объема продаж автомобилей по {}'.format(typ))
    plt.show()
In [159]:
# создам таблицу с группировкой по сегментам и месяцам
dynamics_by_segment = data.groupby(['segmentation', 'date_sale']).sale_usd.sum().reset_index()

dynamics(dynamics_by_segment, 'date_sale', 'segmentation', 'sale_usd', 'sum', 'сегментам')

В целом этот график повторяет график с разделением на коммерческий и некоммерческий транспорт. Также на нем видно, что покупатели в Казахстане предпочитают внедорожники.

Поскольку корреляционный анализ показал высокую взаимосвязь сегмента с типом привода, а также умеренную с типом топлива и с объемом двигателя, то для этих показателей тоже выведу динамическую характеристику. Предполагаю, что все они помогут выявить тип автомобиля, который чаще всего покупают в Казахстане.

Динамика продаж по типу привода

In [160]:
dyn_by_type_drive = (data.query('type_of_drive != "unknown"')
                     .groupby(['type_of_drive', 'date_sale']).sale_usd.sum().reset_index()
                    )

dynamics(dyn_by_type_drive, 'date_sale', 'type_of_drive', 'sale_usd', 'sum', 'типу привода')

Видно, что на рынке лидируют автомобили с полным приводом.

Динамика продаж по типу топлива

In [161]:
dyn_by_type_fuel = (data.query('type_of_fuel != 0')
                     .groupby(['type_of_fuel', 'date_sale']).sale_usd.sum().reset_index()
                    )

dynamics(dyn_by_type_fuel, 'date_sale', 'type_of_fuel', 'sale_usd', 'sum', 'по типу топлива')

Наибольшая популярность у техники с бензиновым двигателем. Гибридные и электрические автомобили редкость для Казахстана.

Динамика продаж по объему двигателя

In [162]:
dyn_by_eng_class = (data.groupby(['engine_class', 'date_sale']).sale_usd.sum().reset_index())
In [163]:
dynamics(dyn_by_eng_class, 'date_sale', 'engine_class', 'sale_usd', 'sum', 'объему двигателя')

Видно, что чаще всего покупают автомобили со среднем объемом двигателя - от 1,8 до 3,5 л.

Вывод

Исходя из графиков, можно сделать вывод, что в Казахстане чаще всего покупают полноприводные бензиновые внедорожники с объемом двигателя от 1,8 до 3,5.

Тепрь выясню, какие модели внедорожников продавались продавались чаще всего и какие принесли больше выручку.

In [164]:
# посчитаю количество купленных автомобилей
data.query('segmentation == "Внедорожники"').groupby(['brand', 'model'])['quantity'].sum().nlargest(10).reset_index()
Out[164]:
brand model quantity
0 Hyundai tucson 3271
1 Lada 4x4 2469
2 Hyundai creta 1775
3 Toyota lc prado 1711
4 Jac s3 1355
5 Toyota rav4 1213
6 Toyota lc200 779
7 Renault duster 707
8 Hyundai santa fe 639
9 Nissan qashqai 601
In [165]:
# посчитаю выручку
data.query('segmentation == "Внедорожники"').groupby(['brand', 'model'])['sale_usd'].sum().nlargest(10).reset_index()
Out[165]:
brand model sale_usd
0 Hyundai tucson 87315649.16
1 Toyota lc prado 83134112.05
2 Toyota lc200 57893775.93
3 Hyundai creta 35222266.67
4 Toyota rav4 33725128.08
5 Lexus lx 33640569.08
6 Lexus rx 24363857.89
7 Hyundai santa fe 23288661.43
8 Lada 4x4 22338600.00
9 Jac s3 17847414.92

По количеству и наибольшей выручке лидирует Hyundai Tucson. Если смотреть на предыдущие графики, то, скорее всего, чаще всего его покупали в такой комплектации: бензиновый двигатель, полный привод и объем двигателя от 1,8 до 3,5. Интересно, что по количеству проданных автомобилей Lada 4x4 (Niva) попала на 1 место, в то время, как по выручке всего на 8-м.

Поскольку высокая корреляция наблюдается между сегментом и типом привода, построю тепловую карту продаж по сегментам в зависимости от типа привода. И эти данные сопоставлю с количеством конкурентов, разбившихся по сегментам.

In [166]:
# продажи по сегментам в зависимости от типа привода
segment_tpdrive = (data
                   .query('type_of_drive != "unknown"')
                   .pivot_table(index='segmentation',
                                columns='type_of_drive',
                                values='sale_usd',
                                aggfunc='sum')
                  )

# число компаний по сегментам в зависимости от типа привода
segment_opponent = (data
                    .query('type_of_drive != "unknown"')
                    .pivot_table(index='segmentation',
                                 columns='type_of_drive',
                                 values='company',
                                 aggfunc='nunique')
                   )
plt.figure(figsize=(8, 5))
ax1 = plt.subplot(2, 1, 1)
plt.title('Продажи по сегментам в зависимости от типа привода, USD')
sns.heatmap(segment_tpdrive, linewidths=2, fmt=',.0f', cbar=False, cmap='Greens', annot=True, ax=ax1)

ax2 = plt.subplot(2, 1, 2)
plt.title('Число компаний в сегментах в зависимости от типа привода')
sns.heatmap(segment_opponent, linewidths=2, fmt=',.0f', cbar=False, cmap='Greens', annot=True, ax=ax2)

plt.subplots_adjust(hspace = 0.6)
plt.show()

На первой матрице видно, что наибольшую прибыль приносит сегмент внедорожников с полным приводом. На втором месте идут легковые автомобили с передним приводом. На третьем внедорожники также с передним приводом.

Ничего удивительного, что в самых прибыльных сегментах очень серьезная конкуренция. Это хорошо видно не втором графике.

Еще интересно, что минивэнов с передним и роботизированным приводами нет на рынке в Казахстане.

Продажи автомобилей в разрезе бренд/сегмент¶

Посмотрю, как распределяются продажи среди топ-10 брендов по сегментам.

In [167]:
# создаю сводную таблицу сгруппированную по бренду
brand_segment = (data.query('brand in @brand_top_sale.brand.head(10)')
                 .groupby(['brand', 'segmentation'])
                 .sale_usd
                 .sum()
                 .unstack()
                 .reset_index().rename_axis(None, axis=1)
                 )
In [168]:
# строю столбчатую диаграмму обема продаж автомобилей в разрезе бренд/сегмент
fig = px.bar(brand_segment,
             x=segment,
             y='brand',
             barmode='stack'
)


fig.update_layout(
    title="Обем продаж автомобилей в разрезе бренд/сегмент, USD",
    xaxis_title='Объем продаж, USD',
    yaxis_title='Бренд',
    yaxis={'categoryorder':'total ascending'},
    legend_title_text='Сегмент',
    title_x = 0.5,
    width=1000,
    height=500
)
fig.update_traces(textposition='inside')

fig.show()

В топ-10 по объему продаж автомобилей в разрезе бренд/сегмент видно:

  1. Toyota продает только легковые автомобили, внедорожники и пикапы. Причем легковые и внедорожники примерно одинаково покупают.
  2. Hyundai на рынке представляет 4 сегмента: внедорожники, легковые автомобили, коммерческую технику и минивэны. На легковых бренд зарабатывает больше всего.
  3. Lada продает внедорожники, легковые авто и минивэны.
  4. Из 10 брендов 7 продают легковые автомобили. 5 - коммерческие и внедорожники.
  5. 4 бренда в топе моносегменты.

Ради интереса посмотрю, как у брендов разбивается выручка по типу топлива.

In [169]:
type_of_fuel = data.query('type_of_fuel != "unknown"').type_of_fuel.unique()
In [170]:
# подготовка данных для создания графика
brand_fuel = (data.query('brand in @brand_top_sale.brand.head(10)')
                 .groupby(['brand', 'type_of_fuel'])
                 .sale_usd.sum()
                 .unstack()
                 .reset_index().rename_axis(None, axis=1)
                 )
In [171]:
fig = px.bar(brand_fuel,
             x=type_of_fuel,
             y='brand',
             barmode='stack'
)


fig.update_layout(
    title="Объем продаж автомобилей в разрезе бренд/тип топлива, USD",
    xaxis_title='Объем продаж, USD',
    yaxis_title='Бренд',
    yaxis={'categoryorder':'total ascending'},
    legend_title_text='Топливо',
    title_x = 0.5,
    width=1000,
    height=500
)
fig.update_traces(textposition='inside')

fig.show()

Большинство продаж приходится на бензиновые двигатели. Причем у Toyota, Lada, Lexus, Kia, Nizzan продаются только авто с бензиновым двигателем. Камаз единственная компания, которая продает технику только с дизельным двигателем. Также интересно, что в топ-10 попал бренд ANKAI, который поставляет технику с дизельными и электрическими двигателями.

Продажи по маркам в разрезе коммерческого и некоммерческого транспорта¶

In [172]:
comm_nocomm = (data
               .groupby(['brand', 'segmentation'])['sale_usd'].sum()
               .unstack()
               .reset_index()
               .rename_axis(None, axis=1)
               .fillna(0)
              )
comm_nocomm['Некоммерческие автомобили'] = comm_nocomm\
.loc[:,['Внедорожники', 'Легковые автомобили', 'Пикапы', 'Минивэны', 'Минивэны']].sum(axis=1)
comm_nocomm.drop(columns=['Внедорожники', 'Легковые автомобили', 'Пикапы', 'Минивэны', 'Минивэны'], inplace=True)
In [173]:
fig = px.bar(comm_nocomm,
             x='brand',
             y=['Коммерческие автомобили', 'Некоммерческие автомобили'],
             barmode='group'
)

fig.update_layout(
    title="Продажи по маркам в разрезе коммерческого и некоммерческого транспорта, USD",
    xaxis_title='Бренд',
    yaxis_title='Объем продаж, USD',
    xaxis={'categoryorder':'total descending'},
    legend_title_text='',
    title_x = 0.5,
    width=950,
    height=700

)

fig.show()

Большинство производителей специализируются на одном типе автомобилей коммерческие или некоммерческие. Но есть и те, кто производит оба типа, например, Hyundai, Jac, UAZ.

Продажи по регионам¶

Для начала посчитаю, сколько выручили денег автоцентры по каждому региону в общем.

In [174]:
sale_by_region = data.groupby('area')['sale_usd', 'quantity'].sum().reset_index()
In [175]:
# график общей выручки по регионам
sale_by_region = sale_by_region.sort_values(by='sale_usd', ascending=False)
plt.figure(figsize=(15, 8))
plt.bar(sale_by_region.area, sale_by_region.sale_usd)

for x, y, tex in zip(sale_by_region.area, sale_by_region.sale_usd, sale_by_region.sale_usd):
    t = plt.text(x, y, int(tex/1000),
                 horizontalalignment='center',
                 rotation='horizontal',
                 verticalalignment='bottom',
                 fontdict={'color':'black', 'weight':'bold'})

plt.title('Общая выручка с продажи автомобилей в разрезе регионов Казахстана в 2019 году (тыс. USD)')
plt.xticks(rotation=90)

ax = plt.gca()
ax.get_yaxis().set_visible(False)

plt.show()

За 9 месяцев больше всего заработали автоцентров в г. Алматы и г. Нур-Султан. Меньше всего покупали автомобили в Туркестанской области. Теперь посмотрю продажи в разрезе коммерческого и некоммерческого транспорта.

In [176]:
comm_nocomm_area = (data
               .groupby(['area', 'segmentation'])['sale_usd'].sum()
               .unstack()
               .reset_index()
               .rename_axis(None, axis=1)
               .fillna(0)
              )
comm_nocomm_area['Некоммерческие автомобили'] = comm_nocomm_area\
.loc[:,['Внедорожники', 'Легковые автомобили', 'Пикапы', 'Минивэны', 'Минивэны']].sum(axis=1)
comm_nocomm_area.drop(columns=['Внедорожники', 'Легковые автомобили', 'Пикапы', 'Минивэны', 'Минивэны'], inplace=True)
In [177]:
# график для сравнения продаж в разрезе коммерческих и некоммерческих автомобилей
fig = px.bar(comm_nocomm_area,
             x='area',
             y=['Коммерческие автомобили', 'Некоммерческие автомобили'],
             barmode='group'
)

# добавляю название и подписи осей
fig.update_layout(
    title="Продажи по маркам в разрезе коммерческого и некоммерческого транспорта, USD",
    xaxis_title='Область',
    yaxis_title='Объем продаж, USD',
    xaxis={'categoryorder':'total descending'},
    legend_title_text='',
    title_x = 0.5,
    width=1000,
    height=800

)

fig.show()

Город Алматы лидирует в продажах и коммерческой и некоммерческой техники, что не удивительно - это самый большой город в Казахстане по населению. На втором месте идет столица Нур-Султан, теперь Астана.

Посмотрю, как по регионам распределялась средняя стоимость продажи.

In [178]:
# добавляю столбец средней продажи по регионам
sale_by_region['mean_sale'] = sale_by_region.sale_usd / sale_by_region.quantity
In [179]:
# график средней цены продажи по регионам
sale_by_region = sale_by_region.sort_values(by='mean_sale', ascending=False)
plt.figure(figsize=(15, 8))
plt.bar(sale_by_region.area, sale_by_region.mean_sale)

for x, y, tex in zip(sale_by_region.area, sale_by_region.mean_sale, sale_by_region.mean_sale):
    t = plt.text(x, y, int(tex),
                 horizontalalignment='center',
                 rotation='horizontal',
                 verticalalignment='bottom',
                 fontdict={'color':'black', 'weight':'bold'})

plt.title('Средняя стоимость продажи автомобилей в разрезе регионов Казахстана в 2019 году')
plt.xticks(rotation=90)

ax = plt.gca()
ax.get_yaxis().set_visible(False)

plt.show()

В Туркестанской области наибольшая средняя стоимость продажи автомобилей, на 14 тыс. долларов больше, чем в г. Алматы. При этом в ней покупают меньше всего 19 автомобилей за 9 месяцев. Возможно, люди ездят покупать автомобили в другие области или в соседний Узбекистан, так как в ней мало диллерских центров или только с люксовыми автомобилями.

Посмотрю динамику продаж по топ-5 регионам.

In [180]:
top_5_reg = sale_by_region.sort_values('sale_usd', ascending=False).head(5)
In [181]:
area_sale_date = data.groupby(['area', 'date_sale'])['sale_usd', 'quantity'].sum().reset_index()
area_sale_date_t_5 = area_sale_date.query('area in @top_5_reg.area')
dynamics(area_sale_date_t_5, 'date_sale', 'area', 'sale_usd', 'sum', 'регионам')

Похоже, что на общую динамику продаж значительно повлияли продажи в Костанайской боласти и незначительно в г. Нур-Султане. Посмотрю, как в динамике менялась средняя стоимось автомобилей в этих 5-ти регионах.

In [182]:
area_sale_date['mean_sale'] = area_sale_date.sale_usd / area_sale_date.quantity
In [183]:
top_5_reg_mean = sale_by_region.sort_values('mean_sale', ascending=False).head(5)
top_5_reg_mean
Out[183]:
area sale_usd quantity mean_sale
13 Туркестанская область 852711.16 19 44879.53
16 г.Алматы 426474005.34 13947 30578.19
17 г.Нур-Султан 292675683.31 10158 28812.33
8 Костанайская область 88334490.44 3305 26727.53
1 Актюбинская область 44215177.52 1809 24441.78
In [184]:
area_sale_date_mean = area_sale_date.query('area in @top_5_reg_mean.area')\
.groupby(['area', 'date_sale'])['mean_sale'].sum().reset_index()
In [185]:
area_sale_date_mean = (area_sale_date_mean
                       .pivot_table(index='date_sale',
                                    columns='area',
                                    values='mean_sale')
                      )
# устанавливаю размеры графиков
color = ['green', 'b', 'r', 'black', 'y']
area_sale_date_mean.plot(figsize=(15, 8), marker='s', color=color)

plt.xlabel('Месяц продажи')
plt.ylabel('Средняя стоимось продаж, млн. USD')
plt.title('Динамика средней стоимости продаж по топ-5 регионов по средней стоимости')
plt.show()

На графике видно, что резко выросла средняя соимость продаж в июле у Костанайской области. Поскольку в этой области развита промышленность, в том числе и автомобильная. Предполагаю, что одно из предприятий получило деньги за крупный промышленный заказ, поэтому и средняя цена выросла. Проверю.

In [186]:
kostanai = data.query('area == "Костанайская область"')\
.groupby(['date_sale', 'segmentation'])['sale_usd'].sum().reset_index()
In [187]:
dynamics(kostanai, 'date_sale', 'segmentation', 'sale_usd', 'sum', 'сегментам в Костанайской области')

Да, мое предположение подтвердилось. В июле была большая продажа в Костанайской области коммерческой техники. Посмотрю, что это были за продажи.

In [188]:
 data.query('area == "Костанайская область" and quantity >= 10').head()
Out[188]:
year month company brand model year_of_release producing_country type_of_fuel engine_volume_l transmission type_of_drive client_type quantity price_usd sale_usd area segmentation class_2013 date_sale engine_class price_cat
13284 2019 2 БИПЭК АВТО Lada 4x4 2018 Республика Казахстан бензин 1.69 механика 4WD юрлицо 11 9000.00 99000.00 Костанайская область Внедорожники Субкомпактные SUV 2019-02 малый низкая
13285 2019 2 БИПЭК АВТО Lada 4x4 2019 Республика Казахстан бензин 1.69 механика 4WD юрлицо 18 9000.00 162000.00 Костанайская область Внедорожники Субкомпактные SUV 2019-02 малый низкая
13308 2019 3 БИПЭК АВТО Lada 4x4 2019 Республика Казахстан бензин 1.69 механика 4WD юрлицо 10 9000.00 90000.00 Костанайская область Внедорожники Субкомпактные SUV 2019-03 малый низкая
13343 2019 4 БИПЭК АВТО Lada 4x4 2019 Республика Казахстан бензин 1.69 механика 4WD юрлицо 28 9000.00 252000.00 Костанайская область Внедорожники Субкомпактные SUV 2019-04 малый низкая
13379 2019 5 БИПЭК АВТО Lada 4x4 2019 Республика Казахстан бензин 1.69 механика 4WD юрлицо 11 9000.00 99000.00 Костанайская область Внедорожники Субкомпактные SUV 2019-05 малый низкая

Такие большие продажи совершили автомобильный хлдинг БИПЭК АВТО и Volkswagen Group Rus. Эти продажи повлияли на динамику продаж в июле.

Продажи автоцентров¶

In [189]:
sale_company = data.groupby('company')['sale_usd', 'quantity'].sum().reset_index()

С большим отрывом лидирует Toyota Motor Kazakhstan, на втором месте Astana Motors, на третьем - БИПЭК АВТО. Посмотрю, какую долю рынка занимают топ-10 компаний.

In [190]:
# диаграмма для емкости рынка в денежном выражении
fig = go.Figure(go.Pie(labels=sale_company['company'].head(10),
                     values=sale_company['sale_usd'],
                     texttemplate = "%{label} <br>%{percent: .1%}",
                     hole=.4,
                     domain=dict(x=[1, 1]),
                     )
      )

# добавляю название
fig.update_layout(
    title="Доля автоцентров на рынке Казахстана в денежном выражении",
    title_x = 0.45
)
In [191]:
# диаграмма для количественной емкости рынка
fig = go.Figure(go.Pie(labels=sale_company['company'].head(10),
                     values=sale_company['quantity'],
                     texttemplate = "%{label} <br>%{percent: .1%}",
                     hole=.4,
                     domain=dict(x=[1, 1])
                     )
      )

# добавляю название
fig.update_layout(
    title="Доля автоцентров на рынке Казахстана в количественном выражении",
    title_x = 0.45
)

fig.show()

По обоим показателям первое место занимает Astana Motors. На втором месте с большим отставанием Allur Auto. Ни в одном топе не присутствует интересующий нас автоцентр Мерку Авто.

Теперь посчитаю какие компании в каких регионах присутствуют.

In [192]:
company_in_reg = data.groupby(['company', 'area'])['sale_usd'].sum().unstack().rename_axis(None, axis=1)

Для каждого региона посчитаю топ-10 автоцентров.

In [193]:
def top_comp(df):
    for column in df.columns:
         display(df[column].nlargest(10).reset_index())
In [194]:
top_comp(company_in_reg)
company Акмолинская область
0 БИПЭК АВТО 5222572.46
1 Astana Motors 3070319.36
2 Вираж 2721749.61
3 СемАЗ 322199.11
4 Subaru Kazakhstan 231228.66
5 Ravon Motors Kazakstan 129034.02
6 Allur Auto 40785.96
7 Renault Россия 27022.73
8 УзАвто-Казахстан 20136.11
9 Almaty Motors Premium NaN
company Актюбинская область
0 Toyota Motor Kazakhstan 14812672.48
1 ТК КАМАЗ 7802142.98
2 Astana Motors 7369315.17
3 БИПЭК АВТО 6777640.88
4 Nissan Manufacturing RUS 2269416.36
5 Вираж 2167233.34
6 Subaru Kazakhstan 757996.30
7 Renault Россия 623715.15
8 СемАЗ 549106.76
9 MMC RUS 408091.48
company Алматинская область
0 БИПЭК АВТО 3286017.75
1 Astana Motors 2945799.35
2 Subaru Kazakhstan 79377.60
3 Вираж 73419.59
4 Allur Auto NaN
5 Almaty Motors Premium NaN
6 Autokapital NaN
7 Caspian Motors NaN
8 Daewoo Bus Kazakhstan NaN
9 Eurasia Motor Premium NaN
company Атырауская область
0 Toyota Motor Kazakhstan 23949899.11
1 БИПЭК АВТО 15897406.00
2 Astana Motors 12284261.85
3 Allur Auto 5090260.02
4 Вираж 4594560.91
5 Nissan Manufacturing RUS 2883975.68
6 Daewoo Bus Kazakhstan 2749639.39
7 Volkswagen Group Rus 1522412.60
8 Autokapital 1086856.80
9 Renault Россия 953436.00
company Восточно-Казахстанская область
0 БИПЭК АВТО 19068068.30
1 Toyota Motor Kazakhstan 9425917.28
2 Daewoo Bus Kazakhstan 9213385.58
3 Astana Motors 5733479.02
4 СемАЗ 5065630.57
5 Вираж 3762044.17
6 Автоцентр-Бавария 3529613.68
7 Subaru Kazakhstan 1140781.85
8 Renault Россия 1130110.83
9 Ravon Motors Kazakstan 998181.18
company Жамбылская область
0 БИПЭК АВТО 5699353.00
1 СемАЗ 2215809.21
2 Astana Motors 1122531.21
3 Toyota Motor Kazakhstan 458151.43
4 Вираж 387491.66
5 Ravon Motors Kazakstan 198873.51
6 Allur Auto 12711.11
7 Almaty Motors Premium NaN
8 Autokapital NaN
9 Caspian Motors NaN
company Западно-Казахстанская область
0 Toyota Motor Kazakhstan 13555587.19
1 БИПЭК АВТО 10347066.34
2 Astana Motors 8908826.05
3 Вираж 2853351.08
4 ТК КАМАЗ 2622083.55
5 Renault Россия 1919264.26
6 Subaru Kazakhstan 1071360.43
7 Автоцентр-Бавария 1041476.23
8 Volkswagen Group Rus 998991.95
9 Nissan Manufacturing RUS 918141.68
company Карагандинская область
0 Toyota Motor Kazakhstan 16875060.51
1 БИПЭК АВТО 10707107.59
2 Astana Motors 10364320.69
3 Автомир-Центр 4151133.25
4 Вираж 4136348.38
5 Renault Россия 3187260.88
6 Nissan Manufacturing RUS 2977020.78
7 MMC RUS 1959568.85
8 Subaru Kazakhstan 1519271.25
9 Volkswagen Group Rus 1451792.57
company Костанайская область
0 Allur Auto 28201849.90
1 Toyota Motor Kazakhstan 20522793.62
2 Astana Motors 11216150.80
3 БИПЭК АВТО 10788925.46
4 Volkswagen Group Rus 2998161.99
5 Вираж 2642381.55
6 Nissan Manufacturing RUS 2397493.44
7 MMC RUS 2017076.99
8 Renault Россия 1567240.40
9 Автодом Motors KST 1064732.01
company Кызылординская область
0 БИПЭК АВТО 4234788.25
1 Astana Motors 2995757.61
2 Вираж 1374207.93
3 Ravon Motors Kazakstan 834280.31
4 Toyota Motor Kazakhstan 824513.45
5 Renault Россия 556134.57
6 Allur Auto 271476.06
7 Лифан Моторс Рус 215286.98
8 Subaru Kazakhstan 118386.31
9 СемАЗ 69045.43
company Мангистауская область
0 Toyota Motor Kazakhstan 15850073.90
1 ТК КАМАЗ 9351089.57
2 БИПЭК АВТО 8204688.25
3 Astana Motors 7287601.05
4 Вираж 2033862.70
5 Nissan Manufacturing RUS 1626020.21
6 Ravon Motors Kazakstan 1310025.04
7 Renault Россия 567626.38
8 УзАвто-Казахстан 211565.35
9 СемАЗ 99107.90
company Павлодарская область
0 Toyota Motor Kazakhstan 12897853.50
1 БИПЭК АВТО 9513646.51
2 Astana Motors 7402731.48
3 Вираж 2369070.67
4 Nissan Manufacturing RUS 1632021.18
5 Renault Россия 514212.56
6 Allur Auto 493518.27
7 Ravon Motors Kazakstan 436666.87
8 MMC RUS 267865.06
9 Subaru Kazakhstan 261410.73
company Северо-Казахстанская область
0 БИПЭК АВТО 5947529.13
1 Astana Motors 2332989.19
2 Вираж 2298781.65
3 Nissan Manufacturing RUS 695002.03
4 Subaru Kazakhstan 523816.76
5 ТК КАМАЗ 473531.51
6 Toyota Motor Kazakhstan 425185.81
7 Renault Россия 354468.91
8 Allur Auto 259118.76
9 СемАЗ 40787.98
company Туркестанская область
0 Toyota Motor Kazakhstan 716037.80
1 Daewoo Bus Kazakhstan 97134.57
2 Ravon Motors Kazakstan 39538.78
3 Allur Auto NaN
4 Almaty Motors Premium NaN
5 Astana Motors NaN
6 Autokapital NaN
7 Caspian Motors NaN
8 Eurasia Motor Premium NaN
9 Hino Motors NaN
company Экспорт область
0 Allur Auto 20244728.31
1 Toyota Motor Kazakhstan 14326265.46
2 Astana Motors 3176002.29
3 СВС-ТРАНС 115025.91
4 Ravon Motors Kazakstan 20030.49
5 Almaty Motors Premium NaN
6 Autokapital NaN
7 Caspian Motors NaN
8 Daewoo Bus Kazakhstan NaN
9 Eurasia Motor Premium NaN
company Южно-Казахстанская область
0 Toyota Motor Kazakhstan 32531266.09
1 Astana Motors 23175025.31
2 БИПЭК АВТО 12249305.63
3 Ravon Motors Kazakstan 2769943.26
4 Вираж 2440821.17
5 Nissan Manufacturing RUS 2437290.09
6 Renault Россия 2313727.80
7 ТК КАМАЗ 1446442.66
8 Subaru Kazakhstan 1041105.08
9 Allur Auto 915325.36
company г.Алматы
0 Toyota Motor Kazakhstan 176926727.19
1 Astana Motors 64098180.29
2 БИПЭК АВТО 31079212.50
3 Nissan Manufacturing RUS 18821630.07
4 Mercur Auto 15389449.83
5 Autokapital 14049545.64
6 Автоцентр-Бавария 12427881.86
7 СВС-ТРАНС 10198392.79
8 Hyundai Com Trans Kazakhstan 9710608.68
9 Almaty Motors Premium 9657735.08
company г.Нур-Султан
0 Toyota Motor Kazakhstan 125778434.62
1 Astana Motors 55855313.34
2 БИПЭК АВТО 26994441.63
3 ТК КАМАЗ 18729260.83
4 Автоцентр-Бавария 11582852.50
5 Nissan Manufacturing RUS 8812003.71
6 TERRA MOTORS 7978608.46
7 Вираж 4888668.23
8 Renault Россия 4745720.60
9 Автомир-Центр 4726388.78

Из 18 областей в топ-10 по выручке Mercur Auto попал в г.Алматы.

Модели-лидеры на рынке¶

In [195]:
top_model = data.groupby('model')['sale_usd', 'quantity'].sum().reset_index()
In [196]:
# график топ-10 авто на рынке в долларах США
top_model = top_model.sort_values(by='sale_usd', ascending=False).head(10)
plt.figure(figsize=(15, 8))
plt.bar(top_model.model, top_model.sale_usd)

for x, y, tex in zip(top_model.model, top_model.sale_usd, top_model.sale_usd):
    t = plt.text(x, y, int(tex),
                 horizontalalignment='center',
                 rotation='horizontal',
                 verticalalignment='bottom',
                 fontdict={'color':'black', 'weight':'bold'})

plt.title('Топ-10 моделей на рынке в долларах США')
plt.xticks(rotation=0)

ax = plt.gca()
ax.get_yaxis().set_visible(False)

plt.show()

На первом месте по продажам Toyota Camry с большим отрывом. Hyundai Tucson на втором месте.

In [197]:
# график топ-10 авто на рынке в количественном выражении
top_model = top_model.sort_values(by='quantity', ascending=False).head(10)
plt.figure(figsize=(15, 8))
plt.bar(top_model.model, top_model.quantity)

for x, y, tex in zip(top_model.model, top_model.quantity, top_model.quantity):
    t = plt.text(x, y, int(tex),
                 horizontalalignment='center',
                 rotation='horizontal',
                 verticalalignment='bottom',
                 fontdict={'color':'black', 'weight':'bold'})

plt.title('Топ-10 моделей на рынке в количественном выражении')
plt.xticks(rotation=0)

ax = plt.gca()
ax.get_yaxis().set_visible(False)

plt.show()

Топ-10 практически не изменился.

Теперь посмотрю, как выглядит топ в разрезе на коммерческий и некоммерческий транспорт.

In [198]:
top_comm = data.query('segmentation == "Коммерческие автомобили"')\
.groupby('model')['sale_usd', 'quantity'].sum().reset_index()
top_comm.sort_values('sale_usd', ascending=False).head(10)
Out[198]:
model sale_usd quantity
71 hff6850g 19592899.74 240
8 3302 15664493.09 998
37 65115 12584301.64 259
17 43118 9818010.24 188
75 next 8929641.52 417
88 tg 7799873.32 69
46 bc 095 6055573.94 80
87 sx3258dr384 5468192.15 101
13 3909 4838746.38 390
40 6520 4683700.32 74
In [199]:
# сртою столбчатую диаграмму для коммерческих моделей
fig = px.bar(top_comm.sort_values('sale_usd', ascending=False).head(10),
             x='sale_usd',
             y='model',
             color = 'model',
             hover_name="model"
            )

# добавляю название и подписи осей
fig.update_layout(
    title="Топ-10 коммерческих моделей по продажам в USD",
    xaxis_title='Продажи, USD',
    yaxis_title='Модель',
    title_x = 0.5
)

fig.show()

В топ-10 на первом месте ANKAI hff6850g, далее идет Камаз 3302.

In [200]:
top_noncomm = data.query('segmentation != "Коммерческие автомобили"')\
.groupby('model')['sale_usd', 'quantity'].sum().reset_index()
In [201]:
# сртою столбчатую диаграмму для некоммерческих моделей
fig = px.bar(top_noncomm.sort_values('sale_usd', ascending=False).head(10),
             x='sale_usd',
             y='model',
             color = 'model',
             hover_name="model"
            )

# добавляю название и подписи осей
fig.update_layout(
    title="Топ-10 некоммерческих моделей по продажам в USD",
    xaxis_title='Продажи, USD',
    yaxis_title='Модель',
    title_x = 0.5
)

fig.show()

График некоммерческих моделей не отличается от графика по всему рынку. Первое место также занимает Toyota Camry, второе Tucson.

Выводы¶

  • За 9 первые месяцев 2019 года в Казахстане было продано 53875 автомобиля из них 5246 или 9,7 % приходится на коммерческий транспорт, 90,3 % или 48629 автомобилей на некоммерческий транспорт. Выручка со всех продаж составила 1 миллиард 349 миллионов долларов. 185 миллионов 811 тысяч или 13,8 % приходится на коммерческий транспорт, 86,2 % или 1 миллиард 163 миллиона - на некоммерческий.
  • По количеству вырученных денег с большим отрывом лидирует Toyota, на втором месте Hyundai, на третьем Lada. По количеству проданных автомобилей рейтинг выглядит иначе. Lada на первом месте, 2-е у Toyota, 3-е Hyundai. Разрыв между топ-3 не сильный. Расположение брендов с 4-го по 10-е место сильно отличаются:
    • В топ-10 по выручке входят: Lexus, KAMAZ, Kia, Nissan, GAZ, Jac, ANKAI.
    • В топ-10 по количеству штук: Kia, Jac, GAZ, Renault, Ravon, Nissan, UAZ.
  • Коммерческий сегмент Среди коммерческого транспорта первое место на рынке 28,4 % занимает Камаз, на втором GAZ - 19,9 %. В Казахстане юрлица предпочитают покупать автомобили из России. Третье место с 17,8 % у китайского бренда ANKAI. Эти три марки занимают почти 66 % рынка. 55 % рынка занимают Российские производители транспорта.
  • Некоммерческий сегмент В нем лидируют японский бренд Toyota, который занимает 40 % потребительского рынка, далее идет Hyundai с 22,8 %. Третье место Lada - 10,7 %. Эти три марки захватили 73 % всего рынка. Японские производители занимают 56 % рынка.
  • По количеству и наибольшей выручке лидирует Hyundai Tucson. Если смотреть на предыдущие графики, то, скорее всего, чаще всего его покупали в такой комплектации: бензиновый двигатель, полный привод и объем двигателя от 1,8 до 3,5. По количеству проданных автомобилей Lada 4x4 (Niva) попала на 1 место, в то время, как по выручке всего на 8-м.
  • наибольшую прибыль приносит сегмент внедорожников с полным приводом. На втором месте идут легковые автомобили с передним приводом. На третьем внедорожники с передним. В этих сегментах серьезная конкуренция
  • Большинство продаж приходится на бензиновые двигатели. Причем у Toyota, Lada, Lexus, Kia, Nizzan продаются только авто с бензиновым двигателем. Камаз единственная компания, которая продает технику только с дизельным двигателем.
  • Большинство производителей специализируются на одном типе автомобилей коммерческие или некоммерческие. Но есть и те, кто производит оба типа, например, Hyundai, Jac, UAZ.
  • Toyota Camry лидер по продажам в Казахстане.
  • За 9 месяцев больше всего заработали автоцентры в г. Алматы и г. Нур-Султан. Меньше всего покупали автомобили в Туркестанской области.
  • Город Алматы лидирует в продажах и коммерческой и некоммерческой техники.
  • В Туркестанской области наибольшая средняя стоимость продажи автомобилей, на 14 тыс. долларов больше, чем в г. Алматы. При этом в ней покупают меньше всего 19 автомобилей за 9 месяцев. Возможно, люди ездят покупать автомобили в другие области или в соседний Узбекистан, так как в ней мало дилерских центров или только с автомобилями класса люкс.
  • В июле была большая продажа в Костанайской области коммерческой техники.
  • Astana Motors лидер среди автоцентров.

Анализ положения Меркур Авто¶

Выручка¶

Общая

In [202]:
mercur_auto = data.query('company == "Mercur Auto"')
print(f'Общая выручка за 9 месяцев 2019 года: {mercur_auto.sale_usd.sum():_.2f} долларов'.replace('_', ' '))
Общая выручка за 9 месяцев 2019 года: 19 355 275.98 долларов

По маркам

In [203]:
mauto_rev_brand = mercur_auto.groupby('brand')['sale_usd', 'quantity'].sum().reset_index()

# выручка по маркам
fig = go.Figure(go.Pie(labels=mauto_rev_brand['brand'],
                       values=mauto_rev_brand['sale_usd'],
                       texttemplate = "%{label} <br>%{percent: .1%}",
                       hole=.4)
               )


# добавляю название
fig.update_layout(
    title="Выручка Mercur Auto по маркам, USD",
    title_x = 0.45
)

fig.show()

Видно, что Меркур Авто сосредоточился на продаже моделей Audi, Porsche, Volkswagen. Последний автоцентру приносит основную выручку - 52,6%.

Средняя

In [204]:
mauto_rev_mean = (mauto_rev_brand.sale_usd.sum()) / (mauto_rev_brand.quantity.sum())
print('Средняя выручка у Меркур Авто:', round(mauto_rev_mean, 2), 'доллара')
Средняя выручка у Меркур Авто: 29777.35 доллара

Ежемесячная

In [205]:
mercur_rev_month = mercur_auto.groupby(['date_sale'])['sale_usd'].sum().reset_index()
In [206]:
# ежемесячная выручка
plt.figure(figsize=(15, 8))
plt.plot(mercur_rev_month.date_sale,
         mercur_rev_month.sale_usd,
         linewidth=2,
         marker='s')

for x, y, tex in zip(mercur_rev_month.date_sale, mercur_rev_month.sale_usd, mercur_rev_month.sale_usd):
    t = plt.text(x, y, int(tex/1000),
                 horizontalalignment='right',
                 rotation='horizontal',
                 verticalalignment='bottom',
                 fontdict={'color':'black', 'weight':'bold'})

plt.title('Ежемесечная выручка Меркур Авто (тыс.USD)')
plt.xlabel('Месяц продажи')
plt.ylabel('Объем продаж, USD')

# убираю ось Y
ax = plt.gca()
ax.get_yaxis().set_visible(False)

plt.show()

Наибольшая выручка была в мае, но после нее очень резкое падение. Буду с этим разбираться, почему так произошло.

Ежемесячная по маркам

In [207]:
# создам таблицу с группировкой по брендам и месяцам
mercur_rev_month_brand = mercur_auto.groupby(['date_sale', 'brand'])['sale_usd'].sum().reset_index()

dynamics(mercur_rev_month_brand, 'date_sale', 'brand', 'sale_usd', 'sum', 'моделям у Меркур Авто')

Видно, что Меркур Авто с июня перестал продавать модели Volkswagen. Возможно, что Volkswagen решил продавать свои автомобили сам. Или аналитики в Меркур Авто ошиблись в расчетах, и компания взяла на реализацию у импортера недостаточное количество автомобилей. И уже к июню все машины были распроданы.

Продажи по регионам¶

In [208]:
# создаю таблицу с группировкой по областям и месяцам
mercur_rev_month_area = mercur_auto.groupby(['date_sale', 'area'])['sale_usd'].sum().reset_index()
In [209]:
# общие продажи по регионам
fig = go.Figure(go.Pie(labels=mercur_rev_month_area['area'],
                       values=mercur_rev_month_area['sale_usd'],
                       texttemplate = "%{label} <br>%{percent: .1%}",
                       hole=.4)
               )


# добавляю название
fig.update_layout(
    title="Доля выручки Меркур Авто по регионам, USD",
    title_x = 0.45
)

fig.show()

79,5 % продаж у Меркур Авто приходится на г. Алматы. 8,5 % На г. Нур-Султан.

Теперь, посмотрю в динамике, как менялась выручка по регионам.

In [210]:
dynamics(mercur_rev_month_area, 'date_sale', 'area', 'sale_usd', 'sum', 'по областям у Меркур Авто')

Видно, что во всех регионах, кроме г. Алматы пропали продажи. Похоже, что в них продавали только Volkswagen, либо был спрос только на них. Это рассуждение проверю построив динамику продаж по маркам.

In [211]:
# функция для графиков динамики продаж
def dynamics(df):
    for i, z in zip(df.area.unique(), range(len(df.area.unique()))):
        color = ['green', 'b', 'r', 'black', 'y', 'm', 'c']
        (df[df['area'] == i]
        .pivot_table(index='date_sale',
                     columns='brand',
                     values='sale_usd',
                     aggfunc='sum')
        .plot(figsize=(8, 4), marker='s', color=color)
        )

        plt.xlabel('Месяц продажи')
        plt.ylabel('Объем продаж, USD')
        plt.title('Динамика объема продаж автомобилей по {}'.format(i))
        plt.show()
In [212]:
dynamics(mercur_auto)

На графиках видно, что с уходом из Меркур Авто Volkswagen и так не высокие продажи снизились еще больше.

Конкурентный анализ¶

Чтобы понять какие конкуренты, надо посмотреть в каком сегменте продает автомобили Меркур Авто.

In [213]:
# классы автомобилей, в которых представлен Меркур Авто
class_mercur = mercur_auto.class_2013.unique()

# регионы в которых продает Меркур Авто
area_mercur = mercur_auto.area.unique()
area_mercur
Out[213]:
array(['г.Алматы', 'г.Нур-Султан', 'Костанайская область',
       'Атырауская область', 'Карагандинская область',
       'Западно-Казахстанская область'], dtype=object)
In [214]:
# сводная таблица по продажам в сегментах по регионам для Меркур Авто
sale_area_mercur = (mercur_auto
                          .pivot_table(index='class_2013',
                                       columns='area',
                                       values='sale_usd',
                                       aggfunc='sum',
                                       fill_value=0)
                         )
In [215]:
# общая сводная таблица продаж по сегментам для всех регионов
all_sale_area = pd.pivot_table(data.query('class_2013 in @class_mercur and area in @area_mercur'),
                               index='class_2013',
                               columns='area',
                               values='sale_usd',
                               aggfunc='sum',
                              fill_value=0)
In [216]:
# соединю таблицы sale_area_mercur и all_sale_area
total = sale_area_mercur.merge(all_sale_area, on='class_2013', how='left', suffixes=('', '2'))
In [217]:
# цикл для получения доли продаж Меркур Авто по регионам
for col in total.columns:
    if not col.endswith('2'):
        total[col] = total[col] / total[col + '2']
        
# оставляю нужные столбцы        
total = total[['г.Алматы', 'г.Нур-Султан', 'Костанайская область',
       'Атырауская область', 'Карагандинская область',
       'Западно-Казахстанская область']]
In [218]:
# создаю тепловую карту, которая покажет долю рынка Меркур Авто по сегментам в регионах
plt.figure(figsize=(8, 5))
plt.title('Доля рынка по региону/сегменту у Меркур Авто')
sns.heatmap(total, linewidths=1, fmt=".1%", cbar=False, cmap='Greens', annot=True, vmin=0, center=.18);

Лучшие сегменты у Меркур Авто в г. Алматы:

  1. F класс – 42,1 %
  2. Спортивные автомобили -28,9 %
  3. Полноразмерные Минивэны - 28,3%

Также важными для Меркур Авто являются: В и E классы в г. Алматы, полноразмерный минивэн в Костанайской области.

Посмотрю, какие есть конкуренты у Меркур Авто в этом городе по маркам автомобилей: Audi, Porsche, Volkswagen.

Конкуренты с Audi

In [219]:
opponents = data.query('area == "г.Алматы" and company != "Mercur Auto" and brand == "Audi"')
opponents.company.unique()
Out[219]:
array([], dtype=object)

Нет компаний, которые бы составляли Меркур Авто конкуренцию с Audi.

Конкуренты с Porsche

In [220]:
opponents = data.query('area == "г.Алматы" and company != "Mercur Auto" and brand == "Porsche"')
opponents.company.unique()
Out[220]:
array([], dtype=object)

Нет компаний, которые бы составляли Меркур Авто конкуренцию с Porsche.

Конкуренты с Volkswagen

In [221]:
opponents = data.query('area == "г.Алматы" and company != "Mercur Auto" and brand == "Volkswagen"')
opponents.company.unique()
Out[221]:
array(['Volkswagen Group Rus'], dtype=object)

Единственный конкурент Меркур Авто - это Volkswagen Group Rus, производитель линейки автомобилей Volkswagen и Skoda в России. И вот этот конкурент в середине 2018 года получил статус официального поставщика представителей VW в Казахстан. И уже через год весь рынок VW взял, скорее всего, под свое крыло.

Матрица Boston Consulting Group¶

Построю матрицу BCG для анализа успешности продажи моделей у Меркур Авто. Для этого подготовлю данные.

In [222]:
# строю таблицу, сгруппированную по модели и дате продажи с агрегацией по сумме продажи
bcg_mercur = mercur_auto.groupby(['model', 'date_sale'])['sale_usd'].sum().reset_index()
In [223]:
# рассчитываю долю выручки по каждой модели от общей выручки автоцентра
bcg_mercur['perc_total'] = (bcg_mercur.groupby('model')['sale_usd'].transform('sum'))\
/ (bcg_mercur.sale_usd.sum())*100
In [224]:
# считаю значение темпа роста цены продажи по каждой модели
bcg_mercur['grow'] = (bcg_mercur.groupby('model')['sale_usd'].transform('last') \
/ bcg_mercur.groupby('model')['sale_usd'].transform('first'))*100
In [225]:
# считаю выручку по каждой модели за весь период наблюдения
bcg_mercur['total'] = bcg_mercur.groupby('model')['sale_usd'].transform('sum')
In [234]:
bcg_mercur.head()
Out[234]:
model date_sale sale_usd perc_total grow total
0 911 carrera s 2019-03 162576.08 0.84 100.00 162576.08
1 a3 2019-05 28115.00 0.31 114.70 60361.99
2 a3 2019-08 32246.99 0.31 114.70 60361.99
3 a4 2019-04 32000.00 0.50 199.56 95858.00
4 a4 2019-07 63858.00 0.50 199.56 95858.00

Таблицу подготовил, теперь построю саму матрицу BCG.

In [233]:
# устанавливаю цвета кругов в зависимости от квадранта
color_scheme = ["#0492C2"] * 11
color_scheme[5] = '#028A0F'
color_scheme[0], color_scheme[2] = '#FCAE1E', '#FCAE1E'

fig = px.scatter(bcg_mercur[bcg_mercur.perc_total > 1.2], # убираю модели, которые приносят очень мало денег
                 x="perc_total", y="grow", 
                 size="total", color="model", 
                 size_max=75, 
                 text='model',
                 color_discrete_sequence=color_scheme,
                 title="BCG matrix Mercur Auto",
                 width=1000, height=600)

# добавляю квадранты матрицы
fig.add_shape(type='line', x0=0, y0=225, x1=40, y1=225, line=dict(color='black', dash='dash', width=3))
fig.add_shape(type='line', x0=20, y0=0, x1=20, y1=500, line=dict(color='black', dash='dash', width=3))

# добавляю подписи квадртантов
fig.add_annotation(x=17.5, y=210, text="<b>Хромые утки</b>", showarrow=False)
fig.add_annotation(x=17.5, y=490, text="<b>Трудные дети</b>", showarrow=False)
fig.add_annotation(x=38.5, y=490, text="<b>Звезды</b>", showarrow=False)
fig.add_annotation(x=37, y=210, text="<b>Дойные коровы</b>", showarrow=False)

# убираю легенду
fig.update_layout(showlegend=False, xaxis_title="Доля в прибыли, %", yaxis_title="Изменение объема продаж, %")

fig.show()

Звезды

У Меркур Авто нет моделей, которые занимают наибольшую долю рынка и одновременно с этим обладают выскоими темпами роста. Наиболее близки к "Звездам" Porshe Cayenne, Volkswagen Tiguan и Audi A6.

Дойные коровы

Для них характерна высокая доля прибыли при невыском темпе роста. В исследуемом автоцентре к ним относится только Volkswagen Polo.

Выводы¶

  • Общая выручка за 9 месяцев 2019 года составляет 19 млн. 355 тыс. долларов.
  • Меркур Авто сосредоточился на продаже моделей Audi, Porsche, Volkswagen.
  • Меркур Авто с июня перестал продавать модели Volkswagen.
  • 79,5 % продаж у Меркур Авто приходится на г. Алматы. 8,5 % На г. Нур-Султан.
  • В г.Алматы Меркур Авто имеет хорошую долю рынка 28,7 % в сегменте полноразмерных SUV и 25,1 % в B классе автомобили.
  • Единственный конкурент Меркур Авто - это Volkswagen Group Rus.
  • Наибольшую прибыль Меркур Авто до июня приносил Volkswagen Polo.
  • К потенциальным "Звездам" можно отнести Porshe Cayenne, Volkswagen Tiguan и Audi A6.

Предложения для Меркурий Авто¶

  1. Поскольку компания является лидером в двух сегментах большого города Алматы, то можно расширить свою линейку автомобилей B класса. Например, если Volkswagen Group Rus не отозвал лицензию, то можно увеличить заказ на Polo и/или в дополнение к нему можно взять Skoda Rapid.
  2. Закрыть убыточные автоцентры и сосредоточиться на развитии двух - трех филиалов. Например, оставить представительство в г. Алматы, г. Нур-Султан и в Атырауской области.
  3. Поскольку Меркур Авто имеет хорошую долю рынка в сегментах: F класс и спортивные автомобили, то можно сосредоточится на реализации автомобилей этих классов Audi и Porshe.